From 15f7ebf37bff6ff04be4a8e5a055011cd0fd01be Mon Sep 17 00:00:00 2001 From: chyyuu Date: Wed, 22 Aug 2012 12:32:13 +0800 Subject: [PATCH] initial version --- LICENSE | 388 +++++ code/lab1/Makefile | 250 +++ code/lab1/boot/asm.h | 26 + code/lab1/boot/bootasm.S | 89 + code/lab1/boot/bootmain.c | 116 ++ code/lab1/kern/debug/assert.h | 27 + code/lab1/kern/debug/kdebug.c | 306 ++++ code/lab1/kern/debug/kdebug.h | 11 + code/lab1/kern/debug/monitor.c | 128 ++ code/lab1/kern/debug/monitor.h | 13 + code/lab1/kern/debug/panic.c | 49 + code/lab1/kern/debug/stab.h | 54 + code/lab1/kern/driver/clock.c | 45 + code/lab1/kern/driver/clock.h | 11 + code/lab1/kern/driver/console.c | 455 +++++ code/lab1/kern/driver/console.h | 11 + code/lab1/kern/driver/intr.c | 15 + code/lab1/kern/driver/intr.h | 8 + code/lab1/kern/driver/kbdreg.h | 84 + code/lab1/kern/driver/picirq.c | 86 + code/lab1/kern/driver/picirq.h | 10 + code/lab1/kern/init/init.c | 104 ++ code/lab1/kern/libs/readline.c | 50 + code/lab1/kern/libs/stdio.c | 78 + code/lab1/kern/mm/memlayout.h | 29 + code/lab1/kern/mm/mmu.h | 174 ++ code/lab1/kern/mm/pmm.c | 99 ++ code/lab1/kern/mm/pmm.h | 7 + code/lab1/kern/trap/trap.c | 187 ++ code/lab1/kern/trap/trap.h | 91 + code/lab1/kern/trap/trapentry.S | 44 + code/lab1/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab1/libs/defs.h | 68 + code/lab1/libs/elf.h | 40 + code/lab1/libs/error.h | 16 + code/lab1/libs/printfmt.c | 340 ++++ code/lab1/libs/stdarg.h | 12 + code/lab1/libs/stdio.h | 24 + code/lab1/libs/string.c | 367 ++++ code/lab1/libs/string.h | 25 + code/lab1/libs/x86.h | 191 ++ code/lab1/tools/function.mk | 95 + code/lab1/tools/gdbinit | 4 + code/lab1/tools/grade.sh | 348 ++++ code/lab1/tools/kernel.ld | 58 + code/lab1/tools/sign.c | 43 + code/lab1/tools/vector.c | 28 + code/lab2/Makefile | 261 +++ code/lab2/boot/asm.h | 26 + code/lab2/boot/bootasm.S | 107 ++ code/lab2/boot/bootmain.c | 116 ++ code/lab2/kern/debug/assert.h | 27 + code/lab2/kern/debug/kdebug.c | 309 ++++ code/lab2/kern/debug/kdebug.h | 12 + code/lab2/kern/debug/monitor.c | 132 ++ code/lab2/kern/debug/monitor.h | 19 + code/lab2/kern/debug/panic.c | 49 + code/lab2/kern/debug/stab.h | 54 + code/lab2/kern/driver/clock.c | 45 + code/lab2/kern/driver/clock.h | 11 + code/lab2/kern/driver/console.c | 465 +++++ code/lab2/kern/driver/console.h | 11 + code/lab2/kern/driver/intr.c | 15 + code/lab2/kern/driver/intr.h | 8 + code/lab2/kern/driver/kbdreg.h | 84 + code/lab2/kern/driver/picirq.c | 86 + code/lab2/kern/driver/picirq.h | 10 + code/lab2/kern/init/entry.S | 49 + code/lab2/kern/init/init.c | 104 ++ code/lab2/kern/libs/readline.c | 50 + code/lab2/kern/libs/stdio.c | 78 + code/lab2/kern/mm/default_pmm.c | 272 +++ code/lab2/kern/mm/default_pmm.h | 9 + code/lab2/kern/mm/memlayout.h | 130 ++ code/lab2/kern/mm/mmu.h | 272 +++ code/lab2/kern/mm/pmm.c | 627 +++++++ code/lab2/kern/mm/pmm.h | 141 ++ code/lab2/kern/sync/sync.h | 28 + code/lab2/kern/trap/trap.c | 187 ++ code/lab2/kern/trap/trap.h | 91 + code/lab2/kern/trap/trapentry.S | 44 + code/lab2/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab2/libs/atomic.h | 251 +++ code/lab2/libs/defs.h | 68 + code/lab2/libs/elf.h | 40 + code/lab2/libs/error.h | 16 + code/lab2/libs/list.h | 163 ++ code/lab2/libs/printfmt.c | 340 ++++ code/lab2/libs/stdarg.h | 12 + code/lab2/libs/stdio.h | 24 + code/lab2/libs/string.c | 367 ++++ code/lab2/libs/string.h | 25 + code/lab2/libs/x86.h | 302 ++++ code/lab2/tools/boot.ld | 15 + code/lab2/tools/function.mk | 95 + code/lab2/tools/gdbinit | 3 + code/lab2/tools/grade.sh | 340 ++++ code/lab2/tools/kernel.ld | 58 + code/lab2/tools/sign.c | 43 + code/lab2/tools/vector.c | 29 + code/lab3/Makefile | 271 +++ code/lab3/boot/asm.h | 26 + code/lab3/boot/bootasm.S | 107 ++ code/lab3/boot/bootmain.c | 116 ++ code/lab3/kern/debug/assert.h | 27 + code/lab3/kern/debug/kdebug.c | 309 ++++ code/lab3/kern/debug/kdebug.h | 12 + code/lab3/kern/debug/monitor.c | 132 ++ code/lab3/kern/debug/monitor.h | 19 + code/lab3/kern/debug/panic.c | 49 + code/lab3/kern/debug/stab.h | 54 + code/lab3/kern/driver/clock.c | 45 + code/lab3/kern/driver/clock.h | 11 + code/lab3/kern/driver/console.c | 465 +++++ code/lab3/kern/driver/console.h | 11 + code/lab3/kern/driver/ide.c | 214 +++ code/lab3/kern/driver/ide.h | 14 + code/lab3/kern/driver/intr.c | 15 + code/lab3/kern/driver/intr.h | 8 + code/lab3/kern/driver/kbdreg.h | 84 + code/lab3/kern/driver/picirq.c | 86 + code/lab3/kern/driver/picirq.h | 10 + code/lab3/kern/fs/fs.h | 12 + code/lab3/kern/fs/swapfs.c | 27 + code/lab3/kern/fs/swapfs.h | 12 + code/lab3/kern/init/entry.S | 49 + code/lab3/kern/init/init.c | 112 ++ code/lab3/kern/libs/readline.c | 50 + code/lab3/kern/libs/stdio.c | 78 + code/lab3/kern/mm/default_pmm.c | 272 +++ code/lab3/kern/mm/default_pmm.h | 9 + code/lab3/kern/mm/memlayout.h | 133 ++ code/lab3/kern/mm/mmu.h | 272 +++ code/lab3/kern/mm/pmm.c | 684 ++++++++ code/lab3/kern/mm/pmm.h | 144 ++ code/lab3/kern/mm/swap.c | 279 +++ code/lab3/kern/mm/swap.h | 65 + code/lab3/kern/mm/swap_fifo.c | 136 ++ code/lab3/kern/mm/swap_fifo.h | 7 + code/lab3/kern/mm/vmm.c | 389 +++++ code/lab3/kern/mm/vmm.h | 51 + code/lab3/kern/sync/sync.h | 28 + code/lab3/kern/trap/trap.c | 226 +++ code/lab3/kern/trap/trap.h | 91 + code/lab3/kern/trap/trapentry.S | 44 + code/lab3/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab3/libs/atomic.h | 251 +++ code/lab3/libs/defs.h | 68 + code/lab3/libs/elf.h | 40 + code/lab3/libs/error.h | 16 + code/lab3/libs/list.h | 163 ++ code/lab3/libs/printfmt.c | 340 ++++ code/lab3/libs/rand.c | 26 + code/lab3/libs/stdarg.h | 12 + code/lab3/libs/stdio.h | 24 + code/lab3/libs/stdlib.h | 12 + code/lab3/libs/string.c | 367 ++++ code/lab3/libs/string.h | 25 + code/lab3/libs/x86.h | 302 ++++ code/lab3/tools/boot.ld | 15 + code/lab3/tools/function.mk | 95 + code/lab3/tools/gdbinit | 3 + code/lab3/tools/grade.sh | 364 ++++ code/lab3/tools/kernel.ld | 58 + code/lab3/tools/sign.c | 43 + code/lab3/tools/vector.c | 29 + code/lab4/Makefile | 275 +++ code/lab4/boot/asm.h | 26 + code/lab4/boot/bootasm.S | 107 ++ code/lab4/boot/bootmain.c | 116 ++ code/lab4/kern/debug/assert.h | 27 + code/lab4/kern/debug/kdebug.c | 309 ++++ code/lab4/kern/debug/kdebug.h | 12 + code/lab4/kern/debug/monitor.c | 132 ++ code/lab4/kern/debug/monitor.h | 19 + code/lab4/kern/debug/panic.c | 49 + code/lab4/kern/debug/stab.h | 54 + code/lab4/kern/driver/clock.c | 45 + code/lab4/kern/driver/clock.h | 11 + code/lab4/kern/driver/console.c | 465 +++++ code/lab4/kern/driver/console.h | 11 + code/lab4/kern/driver/ide.c | 214 +++ code/lab4/kern/driver/ide.h | 14 + code/lab4/kern/driver/intr.c | 15 + code/lab4/kern/driver/intr.h | 8 + code/lab4/kern/driver/kbdreg.h | 84 + code/lab4/kern/driver/picirq.c | 86 + code/lab4/kern/driver/picirq.h | 10 + code/lab4/kern/fs/fs.h | 12 + code/lab4/kern/fs/swapfs.c | 27 + code/lab4/kern/fs/swapfs.h | 12 + code/lab4/kern/init/entry.S | 49 + code/lab4/kern/init/init.c | 113 ++ code/lab4/kern/libs/rb_tree.c | 528 ++++++ code/lab4/kern/libs/rb_tree.h | 32 + code/lab4/kern/libs/readline.c | 50 + code/lab4/kern/libs/stdio.c | 78 + code/lab4/kern/mm/default_pmm.c | 272 +++ code/lab4/kern/mm/default_pmm.h | 9 + code/lab4/kern/mm/kmalloc.c | 635 +++++++ code/lab4/kern/mm/kmalloc.h | 14 + code/lab4/kern/mm/memlayout.h | 140 ++ code/lab4/kern/mm/mmu.h | 272 +++ code/lab4/kern/mm/pmm.c | 665 +++++++ code/lab4/kern/mm/pmm.h | 146 ++ code/lab4/kern/mm/swap.c | 279 +++ code/lab4/kern/mm/swap.h | 65 + code/lab4/kern/mm/swap_fifo.c | 136 ++ code/lab4/kern/mm/swap_fifo.h | 7 + code/lab4/kern/mm/vmm.c | 390 +++++ code/lab4/kern/mm/vmm.h | 51 + code/lab4/kern/process/entry.S | 10 + code/lab4/kern/process/proc.c | 372 ++++ code/lab4/kern/process/proc.h | 77 + code/lab4/kern/process/switch.S | 30 + code/lab4/kern/schedule/sched.c | 41 + code/lab4/kern/schedule/sched.h | 10 + code/lab4/kern/sync/sync.h | 28 + code/lab4/kern/trap/trap.c | 226 +++ code/lab4/kern/trap/trap.h | 91 + code/lab4/kern/trap/trapentry.S | 49 + code/lab4/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab4/libs/atomic.h | 251 +++ code/lab4/libs/defs.h | 68 + code/lab4/libs/elf.h | 40 + code/lab4/libs/error.h | 16 + code/lab4/libs/hash.c | 18 + code/lab4/libs/list.h | 163 ++ code/lab4/libs/printfmt.c | 340 ++++ code/lab4/libs/rand.c | 26 + code/lab4/libs/stdarg.h | 12 + code/lab4/libs/stdio.h | 24 + code/lab4/libs/stdlib.h | 17 + code/lab4/libs/string.c | 367 ++++ code/lab4/libs/string.h | 25 + code/lab4/libs/x86.h | 302 ++++ code/lab4/tools/boot.ld | 15 + code/lab4/tools/function.mk | 95 + code/lab4/tools/gdbinit | 3 + code/lab4/tools/grade.sh | 351 ++++ code/lab4/tools/kernel.ld | 58 + code/lab4/tools/sign.c | 43 + code/lab4/tools/vector.c | 29 + code/lab5/Makefile | 323 ++++ code/lab5/boot/asm.h | 26 + code/lab5/boot/bootasm.S | 107 ++ code/lab5/boot/bootmain.c | 116 ++ code/lab5/kern/debug/assert.h | 27 + code/lab5/kern/debug/kdebug.c | 351 ++++ code/lab5/kern/debug/kdebug.h | 12 + code/lab5/kern/debug/monitor.c | 132 ++ code/lab5/kern/debug/monitor.h | 19 + code/lab5/kern/debug/panic.c | 49 + code/lab5/kern/debug/stab.h | 54 + code/lab5/kern/driver/clock.c | 45 + code/lab5/kern/driver/clock.h | 11 + code/lab5/kern/driver/console.c | 465 +++++ code/lab5/kern/driver/console.h | 11 + code/lab5/kern/driver/ide.c | 214 +++ code/lab5/kern/driver/ide.h | 14 + code/lab5/kern/driver/intr.c | 15 + code/lab5/kern/driver/intr.h | 8 + code/lab5/kern/driver/kbdreg.h | 84 + code/lab5/kern/driver/picirq.c | 86 + code/lab5/kern/driver/picirq.h | 10 + code/lab5/kern/fs/fs.h | 12 + code/lab5/kern/fs/swapfs.c | 27 + code/lab5/kern/fs/swapfs.h | 12 + code/lab5/kern/init/entry.S | 49 + code/lab5/kern/init/init.c | 113 ++ code/lab5/kern/libs/rb_tree.c | 528 ++++++ code/lab5/kern/libs/rb_tree.h | 32 + code/lab5/kern/libs/readline.c | 50 + code/lab5/kern/libs/stdio.c | 78 + code/lab5/kern/mm/default_pmm.c | 272 +++ code/lab5/kern/mm/default_pmm.h | 9 + code/lab5/kern/mm/kmalloc.c | 640 +++++++ code/lab5/kern/mm/kmalloc.h | 16 + code/lab5/kern/mm/memlayout.h | 169 ++ code/lab5/kern/mm/mmu.h | 272 +++ code/lab5/kern/mm/pmm.c | 759 ++++++++ code/lab5/kern/mm/pmm.h | 145 ++ code/lab5/kern/mm/swap.c | 284 +++ code/lab5/kern/mm/swap.h | 65 + code/lab5/kern/mm/swap_fifo.c | 136 ++ code/lab5/kern/mm/swap_fifo.h | 7 + code/lab5/kern/mm/vmm.c | 508 ++++++ code/lab5/kern/mm/vmm.h | 100 ++ code/lab5/kern/process/entry.S | 10 + code/lab5/kern/process/proc.c | 841 +++++++++ code/lab5/kern/process/proc.h | 89 + code/lab5/kern/process/switch.S | 30 + code/lab5/kern/schedule/sched.c | 52 + code/lab5/kern/schedule/sched.h | 10 + code/lab5/kern/sync/sync.h | 57 + code/lab5/kern/syscall/syscall.c | 101 ++ code/lab5/kern/syscall/syscall.h | 7 + code/lab5/kern/trap/trap.c | 289 ++++ code/lab5/kern/trap/trap.h | 89 + code/lab5/kern/trap/trapentry.S | 49 + code/lab5/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab5/libs/atomic.h | 251 +++ code/lab5/libs/defs.h | 68 + code/lab5/libs/elf.h | 48 + code/lab5/libs/error.h | 33 + code/lab5/libs/hash.c | 18 + code/lab5/libs/list.h | 163 ++ code/lab5/libs/printfmt.c | 343 ++++ code/lab5/libs/rand.c | 26 + code/lab5/libs/stdarg.h | 12 + code/lab5/libs/stdio.h | 24 + code/lab5/libs/stdlib.h | 17 + code/lab5/libs/string.c | 367 ++++ code/lab5/libs/string.h | 25 + code/lab5/libs/unistd.h | 29 + code/lab5/libs/x86.h | 302 ++++ code/lab5/tools/boot.ld | 15 + code/lab5/tools/function.mk | 95 + code/lab5/tools/gdbinit | 3 + code/lab5/tools/grade.sh | 517 ++++++ code/lab5/tools/kernel.ld | 58 + code/lab5/tools/sign.c | 43 + code/lab5/tools/user.ld | 71 + code/lab5/tools/vector.c | 29 + code/lab5/user/badarg.c | 22 + code/lab5/user/badsegment.c | 11 + code/lab5/user/divzero.c | 11 + code/lab5/user/exit.c | 34 + code/lab5/user/faultread.c | 9 + code/lab5/user/faultreadkernel.c | 9 + code/lab5/user/forktest.c | 34 + code/lab5/user/forktree.c | 37 + code/lab5/user/hello.c | 11 + code/lab5/user/libs/initcode.S | 14 + code/lab5/user/libs/panic.c | 28 + code/lab5/user/libs/stdio.c | 62 + code/lab5/user/libs/syscall.c | 72 + code/lab5/user/libs/syscall.h | 14 + code/lab5/user/libs/ulib.c | 48 + code/lab5/user/libs/ulib.h | 36 + code/lab5/user/libs/umain.c | 10 + code/lab5/user/pgdir.c | 11 + code/lab5/user/softint.c | 9 + code/lab5/user/spin.c | 29 + code/lab5/user/testbss.c | 33 + code/lab5/user/waitkill.c | 59 + code/lab5/user/yield.c | 16 + code/lab6/Makefile | 323 ++++ code/lab6/boot/asm.h | 26 + code/lab6/boot/bootasm.S | 107 ++ code/lab6/boot/bootmain.c | 116 ++ code/lab6/kern/debug/assert.h | 27 + code/lab6/kern/debug/kdebug.c | 351 ++++ code/lab6/kern/debug/kdebug.h | 12 + code/lab6/kern/debug/monitor.c | 132 ++ code/lab6/kern/debug/monitor.h | 19 + code/lab6/kern/debug/panic.c | 49 + code/lab6/kern/debug/stab.h | 54 + code/lab6/kern/driver/clock.c | 45 + code/lab6/kern/driver/clock.h | 11 + code/lab6/kern/driver/console.c | 465 +++++ code/lab6/kern/driver/console.h | 11 + code/lab6/kern/driver/ide.c | 214 +++ code/lab6/kern/driver/ide.h | 14 + code/lab6/kern/driver/intr.c | 15 + code/lab6/kern/driver/intr.h | 8 + code/lab6/kern/driver/kbdreg.h | 84 + code/lab6/kern/driver/picirq.c | 86 + code/lab6/kern/driver/picirq.h | 10 + code/lab6/kern/fs/fs.h | 12 + code/lab6/kern/fs/swapfs.c | 27 + code/lab6/kern/fs/swapfs.h | 12 + code/lab6/kern/init/entry.S | 49 + code/lab6/kern/init/init.c | 114 ++ code/lab6/kern/libs/rb_tree.c | 528 ++++++ code/lab6/kern/libs/rb_tree.h | 32 + code/lab6/kern/libs/readline.c | 50 + code/lab6/kern/libs/stdio.c | 78 + code/lab6/kern/mm/default_pmm.c | 272 +++ code/lab6/kern/mm/default_pmm.h | 9 + code/lab6/kern/mm/kmalloc.c | 640 +++++++ code/lab6/kern/mm/kmalloc.h | 16 + code/lab6/kern/mm/memlayout.h | 169 ++ code/lab6/kern/mm/mmu.h | 272 +++ code/lab6/kern/mm/pmm.c | 759 ++++++++ code/lab6/kern/mm/pmm.h | 145 ++ code/lab6/kern/mm/swap.c | 284 +++ code/lab6/kern/mm/swap.h | 65 + code/lab6/kern/mm/swap_fifo.c | 136 ++ code/lab6/kern/mm/swap_fifo.h | 7 + code/lab6/kern/mm/vmm.c | 508 ++++++ code/lab6/kern/mm/vmm.h | 100 ++ code/lab6/kern/process/entry.S | 10 + code/lab6/kern/process/proc.c | 849 +++++++++ code/lab6/kern/process/proc.h | 99 ++ code/lab6/kern/process/switch.S | 30 + code/lab6/kern/schedule/default_sched.c | 58 + code/lab6/kern/schedule/default_sched.h | 9 + .../lab6/kern/schedule/default_sched_stride_c | 133 ++ code/lab6/kern/schedule/sched.c | 172 ++ code/lab6/kern/schedule/sched.h | 70 + code/lab6/kern/sync/sync.h | 57 + code/lab6/kern/syscall/syscall.c | 116 ++ code/lab6/kern/syscall/syscall.h | 7 + code/lab6/kern/trap/trap.c | 290 ++++ code/lab6/kern/trap/trap.h | 89 + code/lab6/kern/trap/trapentry.S | 49 + code/lab6/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab6/libs/atomic.h | 251 +++ code/lab6/libs/defs.h | 68 + code/lab6/libs/elf.h | 48 + code/lab6/libs/error.h | 33 + code/lab6/libs/hash.c | 18 + code/lab6/libs/list.h | 163 ++ code/lab6/libs/printfmt.c | 343 ++++ code/lab6/libs/rand.c | 26 + code/lab6/libs/skew_heap.h | 87 + code/lab6/libs/stdarg.h | 12 + code/lab6/libs/stdio.h | 24 + code/lab6/libs/stdlib.h | 17 + code/lab6/libs/string.c | 367 ++++ code/lab6/libs/string.h | 25 + code/lab6/libs/unistd.h | 31 + code/lab6/libs/x86.h | 302 ++++ code/lab6/tools/boot.ld | 15 + code/lab6/tools/function.mk | 95 + code/lab6/tools/gdbinit | 3 + code/lab6/tools/grade.sh | 582 +++++++ code/lab6/tools/kernel.ld | 58 + code/lab6/tools/sign.c | 43 + code/lab6/tools/user.ld | 71 + code/lab6/tools/vector.c | 29 + code/lab6/user/badarg.c | 22 + code/lab6/user/badsegment.c | 11 + code/lab6/user/divzero.c | 11 + code/lab6/user/exit.c | 34 + code/lab6/user/faultread.c | 9 + code/lab6/user/faultreadkernel.c | 9 + code/lab6/user/forktest.c | 34 + code/lab6/user/forktree.c | 37 + code/lab6/user/hello.c | 11 + code/lab6/user/libs/initcode.S | 14 + code/lab6/user/libs/panic.c | 28 + code/lab6/user/libs/stdio.c | 62 + code/lab6/user/libs/syscall.c | 82 + code/lab6/user/libs/syscall.h | 16 + code/lab6/user/libs/ulib.c | 58 + code/lab6/user/libs/ulib.h | 38 + code/lab6/user/libs/umain.c | 10 + code/lab6/user/matrix.c | 84 + code/lab6/user/pgdir.c | 11 + code/lab6/user/priority.c | 77 + code/lab6/user/softint.c | 9 + code/lab6/user/spin.c | 29 + code/lab6/user/testbss.c | 33 + code/lab6/user/waitkill.c | 59 + code/lab6/user/yield.c | 16 + code/lab7/Makefile | 323 ++++ code/lab7/boot/asm.h | 26 + code/lab7/boot/bootasm.S | 107 ++ code/lab7/boot/bootmain.c | 116 ++ code/lab7/kern/debug/assert.h | 27 + code/lab7/kern/debug/kdebug.c | 351 ++++ code/lab7/kern/debug/kdebug.h | 12 + code/lab7/kern/debug/monitor.c | 132 ++ code/lab7/kern/debug/monitor.h | 19 + code/lab7/kern/debug/panic.c | 49 + code/lab7/kern/debug/stab.h | 54 + code/lab7/kern/driver/clock.c | 45 + code/lab7/kern/driver/clock.h | 11 + code/lab7/kern/driver/console.c | 465 +++++ code/lab7/kern/driver/console.h | 11 + code/lab7/kern/driver/ide.c | 214 +++ code/lab7/kern/driver/ide.h | 14 + code/lab7/kern/driver/intr.c | 15 + code/lab7/kern/driver/intr.h | 8 + code/lab7/kern/driver/kbdreg.h | 84 + code/lab7/kern/driver/picirq.c | 86 + code/lab7/kern/driver/picirq.h | 10 + code/lab7/kern/fs/fs.h | 12 + code/lab7/kern/fs/swapfs.c | 27 + code/lab7/kern/fs/swapfs.h | 12 + code/lab7/kern/init/entry.S | 49 + code/lab7/kern/init/init.c | 114 ++ code/lab7/kern/libs/rb_tree.c | 528 ++++++ code/lab7/kern/libs/rb_tree.h | 32 + code/lab7/kern/libs/readline.c | 50 + code/lab7/kern/libs/stdio.c | 78 + code/lab7/kern/mm/default_pmm.c | 272 +++ code/lab7/kern/mm/default_pmm.h | 9 + code/lab7/kern/mm/kmalloc.c | 640 +++++++ code/lab7/kern/mm/kmalloc.h | 16 + code/lab7/kern/mm/memlayout.h | 169 ++ code/lab7/kern/mm/mmu.h | 272 +++ code/lab7/kern/mm/pmm.c | 759 ++++++++ code/lab7/kern/mm/pmm.h | 145 ++ code/lab7/kern/mm/swap.c | 284 +++ code/lab7/kern/mm/swap.h | 65 + code/lab7/kern/mm/swap_fifo.c | 136 ++ code/lab7/kern/mm/swap_fifo.h | 7 + code/lab7/kern/mm/vmm.c | 508 ++++++ code/lab7/kern/mm/vmm.h | 108 ++ code/lab7/kern/process/entry.S | 10 + code/lab7/kern/process/proc.c | 872 ++++++++++ code/lab7/kern/process/proc.h | 100 ++ code/lab7/kern/process/switch.S | 30 + code/lab7/kern/schedule/default_sched.c | 58 + code/lab7/kern/schedule/default_sched.h | 9 + .../lab7/kern/schedule/default_sched_stride_c | 133 ++ code/lab7/kern/schedule/sched.c | 172 ++ code/lab7/kern/schedule/sched.h | 70 + code/lab7/kern/sync/check_sync.c | 196 +++ code/lab7/kern/sync/monitor.c | 59 + code/lab7/kern/sync/monitor.h | 90 + code/lab7/kern/sync/sem.c | 77 + code/lab7/kern/sync/sem.h | 19 + code/lab7/kern/sync/sync.h | 31 + code/lab7/kern/sync/wait.c | 122 ++ code/lab7/kern/sync/wait.h | 48 + code/lab7/kern/syscall/syscall.c | 123 ++ code/lab7/kern/syscall/syscall.h | 7 + code/lab7/kern/trap/trap.c | 290 ++++ code/lab7/kern/trap/trap.h | 89 + code/lab7/kern/trap/trapentry.S | 49 + code/lab7/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab7/libs/atomic.h | 251 +++ code/lab7/libs/defs.h | 68 + code/lab7/libs/elf.h | 48 + code/lab7/libs/error.h | 33 + code/lab7/libs/hash.c | 18 + code/lab7/libs/list.h | 163 ++ code/lab7/libs/printfmt.c | 343 ++++ code/lab7/libs/rand.c | 26 + code/lab7/libs/skew_heap.h | 87 + code/lab7/libs/stdarg.h | 12 + code/lab7/libs/stdio.h | 24 + code/lab7/libs/stdlib.h | 17 + code/lab7/libs/string.c | 367 ++++ code/lab7/libs/string.h | 25 + code/lab7/libs/unistd.h | 31 + code/lab7/libs/x86.h | 302 ++++ code/lab7/tools/boot.ld | 15 + code/lab7/tools/function.mk | 95 + code/lab7/tools/gdbinit | 3 + code/lab7/tools/grade.sh | 636 +++++++ code/lab7/tools/kernel.ld | 58 + code/lab7/tools/sign.c | 43 + code/lab7/tools/user.ld | 71 + code/lab7/tools/vector.c | 29 + code/lab7/user/badarg.c | 22 + code/lab7/user/badsegment.c | 11 + code/lab7/user/divzero.c | 11 + code/lab7/user/exit.c | 34 + code/lab7/user/faultread.c | 9 + code/lab7/user/faultreadkernel.c | 9 + code/lab7/user/forktest.c | 34 + code/lab7/user/forktree.c | 39 + code/lab7/user/hello.c | 11 + code/lab7/user/libs/initcode.S | 14 + code/lab7/user/libs/panic.c | 28 + code/lab7/user/libs/stdio.c | 62 + code/lab7/user/libs/syscall.c | 87 + code/lab7/user/libs/syscall.h | 18 + code/lab7/user/libs/ulib.c | 63 + code/lab7/user/libs/ulib.h | 39 + code/lab7/user/libs/umain.c | 10 + code/lab7/user/matrix.c | 84 + code/lab7/user/pgdir.c | 11 + code/lab7/user/priority.c | 80 + code/lab7/user/sleep.c | 28 + code/lab7/user/sleepkill.c | 18 + code/lab7/user/softint.c | 9 + code/lab7/user/spin.c | 32 + code/lab7/user/testbss.c | 33 + code/lab7/user/waitkill.c | 59 + code/lab7/user/yield.c | 16 + code/lab8/Makefile | 369 ++++ code/lab8/boot/asm.h | 26 + code/lab8/boot/bootasm.S | 107 ++ code/lab8/boot/bootmain.c | 116 ++ code/lab8/kern/debug/assert.h | 27 + code/lab8/kern/debug/kdebug.c | 351 ++++ code/lab8/kern/debug/kdebug.h | 12 + code/lab8/kern/debug/monitor.c | 132 ++ code/lab8/kern/debug/monitor.h | 19 + code/lab8/kern/debug/panic.c | 49 + code/lab8/kern/debug/stab.h | 54 + code/lab8/kern/driver/clock.c | 49 + code/lab8/kern/driver/clock.h | 14 + code/lab8/kern/driver/console.c | 465 +++++ code/lab8/kern/driver/console.h | 11 + code/lab8/kern/driver/ide.c | 214 +++ code/lab8/kern/driver/ide.h | 14 + code/lab8/kern/driver/intr.c | 15 + code/lab8/kern/driver/intr.h | 8 + code/lab8/kern/driver/kbdreg.h | 84 + code/lab8/kern/driver/picirq.c | 86 + code/lab8/kern/driver/picirq.h | 10 + code/lab8/kern/fs/devs/dev.c | 167 ++ code/lab8/kern/fs/devs/dev.h | 31 + code/lab8/kern/fs/devs/dev_disk0.c | 144 ++ code/lab8/kern/fs/devs/dev_stdin.c | 126 ++ code/lab8/kern/fs/devs/dev_stdout.c | 64 + code/lab8/kern/fs/file.c | 356 ++++ code/lab8/kern/fs/file.h | 60 + code/lab8/kern/fs/fs.c | 99 ++ code/lab8/kern/fs/fs.h | 59 + code/lab8/kern/fs/iobuf.c | 77 + code/lab8/kern/fs/iobuf.h | 24 + code/lab8/kern/fs/sfs/bitmap.c | 114 ++ code/lab8/kern/fs/sfs/bitmap.h | 32 + code/lab8/kern/fs/sfs/sfs.c | 19 + code/lab8/kern/fs/sfs/sfs.h | 129 ++ code/lab8/kern/fs/sfs/sfs_fs.c | 258 +++ code/lab8/kern/fs/sfs/sfs_inode.c | 987 +++++++++++ code/lab8/kern/fs/sfs/sfs_io.c | 167 ++ code/lab8/kern/fs/sfs/sfs_lock.c | 44 + code/lab8/kern/fs/swap/swapfs.c | 27 + code/lab8/kern/fs/swap/swapfs.h | 12 + code/lab8/kern/fs/sysfile.c | 317 ++++ code/lab8/kern/fs/sysfile.h | 28 + code/lab8/kern/fs/vfs/inode.c | 110 ++ code/lab8/kern/fs/vfs/inode.h | 248 +++ code/lab8/kern/fs/vfs/vfs.c | 97 ++ code/lab8/kern/fs/vfs/vfs.h | 191 ++ code/lab8/kern/fs/vfs/vfsdev.c | 309 ++++ code/lab8/kern/fs/vfs/vfsfile.c | 110 ++ code/lab8/kern/fs/vfs/vfslookup.c | 101 ++ code/lab8/kern/fs/vfs/vfspath.c | 126 ++ code/lab8/kern/init/entry.S | 49 + code/lab8/kern/init/init.c | 116 ++ code/lab8/kern/libs/rb_tree.c | 528 ++++++ code/lab8/kern/libs/rb_tree.h | 32 + code/lab8/kern/libs/readline.c | 50 + code/lab8/kern/libs/stdio.c | 78 + code/lab8/kern/libs/string.c | 26 + code/lab8/kern/mm/default_pmm.c | 272 +++ code/lab8/kern/mm/default_pmm.h | 9 + code/lab8/kern/mm/kmalloc.c | 640 +++++++ code/lab8/kern/mm/kmalloc.h | 16 + code/lab8/kern/mm/memlayout.h | 169 ++ code/lab8/kern/mm/mmu.h | 272 +++ code/lab8/kern/mm/pmm.c | 759 ++++++++ code/lab8/kern/mm/pmm.h | 145 ++ code/lab8/kern/mm/swap.c | 284 +++ code/lab8/kern/mm/swap.h | 65 + code/lab8/kern/mm/swap_fifo.c | 136 ++ code/lab8/kern/mm/swap_fifo.h | 7 + code/lab8/kern/mm/vmm.c | 530 ++++++ code/lab8/kern/mm/vmm.h | 109 ++ code/lab8/kern/process/entry.S | 10 + code/lab8/kern/process/proc.c | 896 ++++++++++ code/lab8/kern/process/proc.h | 105 ++ code/lab8/kern/process/switch.S | 30 + code/lab8/kern/schedule/default_sched.c | 58 + code/lab8/kern/schedule/default_sched.h | 9 + .../lab8/kern/schedule/default_sched_stride_c | 133 ++ code/lab8/kern/schedule/sched.c | 172 ++ code/lab8/kern/schedule/sched.h | 70 + code/lab8/kern/sync/check_sync.c | 196 +++ code/lab8/kern/sync/monitor.c | 59 + code/lab8/kern/sync/monitor.h | 90 + code/lab8/kern/sync/sem.c | 77 + code/lab8/kern/sync/sem.h | 19 + code/lab8/kern/sync/sync.h | 31 + code/lab8/kern/sync/wait.c | 122 ++ code/lab8/kern/sync/wait.h | 48 + code/lab8/kern/syscall/syscall.c | 207 +++ code/lab8/kern/syscall/syscall.h | 7 + code/lab8/kern/trap/trap.c | 290 ++++ code/lab8/kern/trap/trap.h | 89 + code/lab8/kern/trap/trapentry.S | 49 + code/lab8/kern/trap/vectors.S | 1536 +++++++++++++++++ code/lab8/libs/atomic.h | 251 +++ code/lab8/libs/defs.h | 79 + code/lab8/libs/dirent.h | 13 + code/lab8/libs/elf.h | 48 + code/lab8/libs/error.h | 33 + code/lab8/libs/hash.c | 18 + code/lab8/libs/list.h | 163 ++ code/lab8/libs/printfmt.c | 359 ++++ code/lab8/libs/rand.c | 26 + code/lab8/libs/skew_heap.h | 87 + code/lab8/libs/stat.h | 27 + code/lab8/libs/stdarg.h | 12 + code/lab8/libs/stdio.h | 24 + code/lab8/libs/stdlib.h | 17 + code/lab8/libs/string.c | 380 ++++ code/lab8/libs/string.h | 28 + code/lab8/libs/unistd.h | 68 + code/lab8/libs/x86.h | 310 ++++ code/lab8/tools/boot.ld | 15 + code/lab8/tools/function.mk | 95 + code/lab8/tools/gdbinit | 3 + code/lab8/tools/grade.sh | 636 +++++++ code/lab8/tools/kernel.ld | 58 + code/lab8/tools/mksfs.c | 582 +++++++ code/lab8/tools/sign.c | 43 + code/lab8/tools/user.ld | 71 + code/lab8/tools/vector.c | 29 + code/lab8/user/badarg.c | 22 + code/lab8/user/badsegment.c | 11 + code/lab8/user/divzero.c | 11 + code/lab8/user/exit.c | 34 + code/lab8/user/faultread.c | 9 + code/lab8/user/faultreadkernel.c | 9 + code/lab8/user/forktest.c | 34 + code/lab8/user/forktree.c | 37 + code/lab8/user/hello.c | 11 + code/lab8/user/libs/dir.c | 46 + code/lab8/user/libs/dir.h | 19 + code/lab8/user/libs/file.c | 68 + code/lab8/user/libs/file.h | 23 + code/lab8/user/libs/initcode.S | 24 + code/lab8/user/libs/lock.h | 42 + code/lab8/user/libs/panic.c | 28 + code/lab8/user/libs/stdio.c | 89 + code/lab8/user/libs/syscall.c | 145 ++ code/lab8/user/libs/syscall.h | 33 + code/lab8/user/libs/ulib.c | 87 + code/lab8/user/libs/ulib.h | 49 + code/lab8/user/libs/umain.c | 34 + code/lab8/user/ls.c | 118 ++ code/lab8/user/matrix.c | 84 + code/lab8/user/pgdir.c | 11 + code/lab8/user/priority.c | 80 + code/lab8/user/sh.c | 254 +++ code/lab8/user/sleep.c | 28 + code/lab8/user/sleepkill.c | 18 + code/lab8/user/softint.c | 9 + code/lab8/user/spin.c | 29 + code/lab8/user/testbss.c | 33 + code/lab8/user/waitkill.c | 59 + code/lab8/user/yield.c | 16 + doc/lab0.pdf | Bin 0 -> 953677 bytes doc/lab1.pdf | Bin 0 -> 933686 bytes doc/lab2.pdf | Bin 0 -> 708103 bytes doc/lab3.pdf | Bin 0 -> 465307 bytes doc/lab4.pdf | Bin 0 -> 480594 bytes doc/lab5.pdf | Bin 0 -> 459658 bytes doc/lab6.pdf | Bin 0 -> 414183 bytes doc/lab7.pdf | Bin 0 -> 391195 bytes doc/lab8.pdf | Bin 0 -> 669168 bytes 743 files changed, 94930 insertions(+) create mode 100644 LICENSE create mode 100644 code/lab1/Makefile create mode 100644 code/lab1/boot/asm.h create mode 100644 code/lab1/boot/bootasm.S create mode 100644 code/lab1/boot/bootmain.c create mode 100644 code/lab1/kern/debug/assert.h create mode 100644 code/lab1/kern/debug/kdebug.c create mode 100644 code/lab1/kern/debug/kdebug.h create mode 100644 code/lab1/kern/debug/monitor.c create mode 100644 code/lab1/kern/debug/monitor.h create mode 100644 code/lab1/kern/debug/panic.c create mode 100644 code/lab1/kern/debug/stab.h create mode 100644 code/lab1/kern/driver/clock.c create mode 100644 code/lab1/kern/driver/clock.h create mode 100644 code/lab1/kern/driver/console.c create mode 100644 code/lab1/kern/driver/console.h create mode 100644 code/lab1/kern/driver/intr.c create mode 100644 code/lab1/kern/driver/intr.h create mode 100644 code/lab1/kern/driver/kbdreg.h create mode 100644 code/lab1/kern/driver/picirq.c create mode 100644 code/lab1/kern/driver/picirq.h create mode 100644 code/lab1/kern/init/init.c create mode 100644 code/lab1/kern/libs/readline.c create mode 100644 code/lab1/kern/libs/stdio.c create mode 100644 code/lab1/kern/mm/memlayout.h create mode 100644 code/lab1/kern/mm/mmu.h create mode 100644 code/lab1/kern/mm/pmm.c create mode 100644 code/lab1/kern/mm/pmm.h create mode 100644 code/lab1/kern/trap/trap.c create mode 100644 code/lab1/kern/trap/trap.h create mode 100644 code/lab1/kern/trap/trapentry.S create mode 100644 code/lab1/kern/trap/vectors.S create mode 100644 code/lab1/libs/defs.h create mode 100644 code/lab1/libs/elf.h create mode 100644 code/lab1/libs/error.h create mode 100644 code/lab1/libs/printfmt.c create mode 100644 code/lab1/libs/stdarg.h create mode 100644 code/lab1/libs/stdio.h create mode 100644 code/lab1/libs/string.c create mode 100644 code/lab1/libs/string.h create mode 100644 code/lab1/libs/x86.h create mode 100644 code/lab1/tools/function.mk create mode 100644 code/lab1/tools/gdbinit create mode 100644 code/lab1/tools/grade.sh create mode 100644 code/lab1/tools/kernel.ld create mode 100644 code/lab1/tools/sign.c create mode 100644 code/lab1/tools/vector.c create mode 100644 code/lab2/Makefile create mode 100644 code/lab2/boot/asm.h create mode 100644 code/lab2/boot/bootasm.S create mode 100644 code/lab2/boot/bootmain.c create mode 100644 code/lab2/kern/debug/assert.h create mode 100644 code/lab2/kern/debug/kdebug.c create mode 100644 code/lab2/kern/debug/kdebug.h create mode 100644 code/lab2/kern/debug/monitor.c create mode 100644 code/lab2/kern/debug/monitor.h create mode 100644 code/lab2/kern/debug/panic.c create mode 100644 code/lab2/kern/debug/stab.h create mode 100644 code/lab2/kern/driver/clock.c create mode 100644 code/lab2/kern/driver/clock.h create mode 100644 code/lab2/kern/driver/console.c create mode 100644 code/lab2/kern/driver/console.h create mode 100644 code/lab2/kern/driver/intr.c create mode 100644 code/lab2/kern/driver/intr.h create mode 100644 code/lab2/kern/driver/kbdreg.h create mode 100644 code/lab2/kern/driver/picirq.c create mode 100644 code/lab2/kern/driver/picirq.h create mode 100644 code/lab2/kern/init/entry.S create mode 100644 code/lab2/kern/init/init.c create mode 100644 code/lab2/kern/libs/readline.c create mode 100644 code/lab2/kern/libs/stdio.c create mode 100644 code/lab2/kern/mm/default_pmm.c create mode 100644 code/lab2/kern/mm/default_pmm.h create mode 100644 code/lab2/kern/mm/memlayout.h create mode 100644 code/lab2/kern/mm/mmu.h create mode 100644 code/lab2/kern/mm/pmm.c create mode 100644 code/lab2/kern/mm/pmm.h create mode 100644 code/lab2/kern/sync/sync.h create mode 100644 code/lab2/kern/trap/trap.c create mode 100644 code/lab2/kern/trap/trap.h create mode 100644 code/lab2/kern/trap/trapentry.S create mode 100644 code/lab2/kern/trap/vectors.S create mode 100644 code/lab2/libs/atomic.h create mode 100644 code/lab2/libs/defs.h create mode 100644 code/lab2/libs/elf.h create mode 100644 code/lab2/libs/error.h create mode 100644 code/lab2/libs/list.h create mode 100644 code/lab2/libs/printfmt.c create mode 100644 code/lab2/libs/stdarg.h create mode 100644 code/lab2/libs/stdio.h create mode 100644 code/lab2/libs/string.c create mode 100644 code/lab2/libs/string.h create mode 100644 code/lab2/libs/x86.h create mode 100644 code/lab2/tools/boot.ld create mode 100644 code/lab2/tools/function.mk create mode 100644 code/lab2/tools/gdbinit create mode 100644 code/lab2/tools/grade.sh create mode 100644 code/lab2/tools/kernel.ld create mode 100644 code/lab2/tools/sign.c create mode 100644 code/lab2/tools/vector.c create mode 100644 code/lab3/Makefile create mode 100644 code/lab3/boot/asm.h create mode 100644 code/lab3/boot/bootasm.S create mode 100644 code/lab3/boot/bootmain.c create mode 100644 code/lab3/kern/debug/assert.h create mode 100644 code/lab3/kern/debug/kdebug.c create mode 100644 code/lab3/kern/debug/kdebug.h create mode 100644 code/lab3/kern/debug/monitor.c create mode 100644 code/lab3/kern/debug/monitor.h create mode 100644 code/lab3/kern/debug/panic.c create mode 100644 code/lab3/kern/debug/stab.h create mode 100644 code/lab3/kern/driver/clock.c create mode 100644 code/lab3/kern/driver/clock.h create mode 100644 code/lab3/kern/driver/console.c create mode 100644 code/lab3/kern/driver/console.h create mode 100644 code/lab3/kern/driver/ide.c create mode 100644 code/lab3/kern/driver/ide.h create mode 100644 code/lab3/kern/driver/intr.c create mode 100644 code/lab3/kern/driver/intr.h create mode 100644 code/lab3/kern/driver/kbdreg.h create mode 100644 code/lab3/kern/driver/picirq.c create mode 100644 code/lab3/kern/driver/picirq.h create mode 100644 code/lab3/kern/fs/fs.h create mode 100644 code/lab3/kern/fs/swapfs.c create mode 100644 code/lab3/kern/fs/swapfs.h create mode 100644 code/lab3/kern/init/entry.S create mode 100644 code/lab3/kern/init/init.c create mode 100644 code/lab3/kern/libs/readline.c create mode 100644 code/lab3/kern/libs/stdio.c create mode 100644 code/lab3/kern/mm/default_pmm.c create mode 100644 code/lab3/kern/mm/default_pmm.h create mode 100644 code/lab3/kern/mm/memlayout.h create mode 100644 code/lab3/kern/mm/mmu.h create mode 100644 code/lab3/kern/mm/pmm.c create mode 100644 code/lab3/kern/mm/pmm.h create mode 100644 code/lab3/kern/mm/swap.c create mode 100644 code/lab3/kern/mm/swap.h create mode 100644 code/lab3/kern/mm/swap_fifo.c create mode 100644 code/lab3/kern/mm/swap_fifo.h create mode 100644 code/lab3/kern/mm/vmm.c create mode 100644 code/lab3/kern/mm/vmm.h create mode 100644 code/lab3/kern/sync/sync.h create mode 100644 code/lab3/kern/trap/trap.c create mode 100644 code/lab3/kern/trap/trap.h create mode 100644 code/lab3/kern/trap/trapentry.S create mode 100644 code/lab3/kern/trap/vectors.S create mode 100644 code/lab3/libs/atomic.h create mode 100644 code/lab3/libs/defs.h create mode 100644 code/lab3/libs/elf.h create mode 100644 code/lab3/libs/error.h create mode 100644 code/lab3/libs/list.h create mode 100644 code/lab3/libs/printfmt.c create mode 100644 code/lab3/libs/rand.c create mode 100644 code/lab3/libs/stdarg.h create mode 100644 code/lab3/libs/stdio.h create mode 100644 code/lab3/libs/stdlib.h create mode 100644 code/lab3/libs/string.c create mode 100644 code/lab3/libs/string.h create mode 100644 code/lab3/libs/x86.h create mode 100644 code/lab3/tools/boot.ld create mode 100644 code/lab3/tools/function.mk create mode 100644 code/lab3/tools/gdbinit create mode 100644 code/lab3/tools/grade.sh create mode 100644 code/lab3/tools/kernel.ld create mode 100644 code/lab3/tools/sign.c create mode 100644 code/lab3/tools/vector.c create mode 100644 code/lab4/Makefile create mode 100644 code/lab4/boot/asm.h create mode 100644 code/lab4/boot/bootasm.S create mode 100644 code/lab4/boot/bootmain.c create mode 100644 code/lab4/kern/debug/assert.h create mode 100644 code/lab4/kern/debug/kdebug.c create mode 100644 code/lab4/kern/debug/kdebug.h create mode 100644 code/lab4/kern/debug/monitor.c create mode 100644 code/lab4/kern/debug/monitor.h create mode 100644 code/lab4/kern/debug/panic.c create mode 100644 code/lab4/kern/debug/stab.h create mode 100644 code/lab4/kern/driver/clock.c create mode 100644 code/lab4/kern/driver/clock.h create mode 100644 code/lab4/kern/driver/console.c create mode 100644 code/lab4/kern/driver/console.h create mode 100644 code/lab4/kern/driver/ide.c create mode 100644 code/lab4/kern/driver/ide.h create mode 100644 code/lab4/kern/driver/intr.c create mode 100644 code/lab4/kern/driver/intr.h create mode 100644 code/lab4/kern/driver/kbdreg.h create mode 100644 code/lab4/kern/driver/picirq.c create mode 100644 code/lab4/kern/driver/picirq.h create mode 100644 code/lab4/kern/fs/fs.h create mode 100644 code/lab4/kern/fs/swapfs.c create mode 100644 code/lab4/kern/fs/swapfs.h create mode 100644 code/lab4/kern/init/entry.S create mode 100644 code/lab4/kern/init/init.c create mode 100644 code/lab4/kern/libs/rb_tree.c create mode 100644 code/lab4/kern/libs/rb_tree.h create mode 100644 code/lab4/kern/libs/readline.c create mode 100644 code/lab4/kern/libs/stdio.c create mode 100644 code/lab4/kern/mm/default_pmm.c create mode 100644 code/lab4/kern/mm/default_pmm.h create mode 100644 code/lab4/kern/mm/kmalloc.c create mode 100644 code/lab4/kern/mm/kmalloc.h create mode 100644 code/lab4/kern/mm/memlayout.h create mode 100644 code/lab4/kern/mm/mmu.h create mode 100644 code/lab4/kern/mm/pmm.c create mode 100644 code/lab4/kern/mm/pmm.h create mode 100644 code/lab4/kern/mm/swap.c create mode 100644 code/lab4/kern/mm/swap.h create mode 100644 code/lab4/kern/mm/swap_fifo.c create mode 100644 code/lab4/kern/mm/swap_fifo.h create mode 100644 code/lab4/kern/mm/vmm.c create mode 100644 code/lab4/kern/mm/vmm.h create mode 100644 code/lab4/kern/process/entry.S create mode 100644 code/lab4/kern/process/proc.c create mode 100644 code/lab4/kern/process/proc.h create mode 100644 code/lab4/kern/process/switch.S create mode 100644 code/lab4/kern/schedule/sched.c create mode 100644 code/lab4/kern/schedule/sched.h create mode 100644 code/lab4/kern/sync/sync.h create mode 100644 code/lab4/kern/trap/trap.c create mode 100644 code/lab4/kern/trap/trap.h create mode 100644 code/lab4/kern/trap/trapentry.S create mode 100644 code/lab4/kern/trap/vectors.S create mode 100644 code/lab4/libs/atomic.h create mode 100644 code/lab4/libs/defs.h create mode 100644 code/lab4/libs/elf.h create mode 100644 code/lab4/libs/error.h create mode 100644 code/lab4/libs/hash.c create mode 100644 code/lab4/libs/list.h create mode 100644 code/lab4/libs/printfmt.c create mode 100644 code/lab4/libs/rand.c create mode 100644 code/lab4/libs/stdarg.h create mode 100644 code/lab4/libs/stdio.h create mode 100644 code/lab4/libs/stdlib.h create mode 100644 code/lab4/libs/string.c create mode 100644 code/lab4/libs/string.h create mode 100644 code/lab4/libs/x86.h create mode 100644 code/lab4/tools/boot.ld create mode 100644 code/lab4/tools/function.mk create mode 100644 code/lab4/tools/gdbinit create mode 100644 code/lab4/tools/grade.sh create mode 100644 code/lab4/tools/kernel.ld create mode 100644 code/lab4/tools/sign.c create mode 100644 code/lab4/tools/vector.c create mode 100644 code/lab5/Makefile create mode 100644 code/lab5/boot/asm.h create mode 100644 code/lab5/boot/bootasm.S create mode 100644 code/lab5/boot/bootmain.c create mode 100644 code/lab5/kern/debug/assert.h create mode 100644 code/lab5/kern/debug/kdebug.c create mode 100644 code/lab5/kern/debug/kdebug.h create mode 100644 code/lab5/kern/debug/monitor.c create mode 100644 code/lab5/kern/debug/monitor.h create mode 100644 code/lab5/kern/debug/panic.c create mode 100644 code/lab5/kern/debug/stab.h create mode 100644 code/lab5/kern/driver/clock.c create mode 100644 code/lab5/kern/driver/clock.h create mode 100644 code/lab5/kern/driver/console.c create mode 100644 code/lab5/kern/driver/console.h create mode 100644 code/lab5/kern/driver/ide.c create mode 100644 code/lab5/kern/driver/ide.h create mode 100644 code/lab5/kern/driver/intr.c create mode 100644 code/lab5/kern/driver/intr.h create mode 100644 code/lab5/kern/driver/kbdreg.h create mode 100644 code/lab5/kern/driver/picirq.c create mode 100644 code/lab5/kern/driver/picirq.h create mode 100644 code/lab5/kern/fs/fs.h create mode 100644 code/lab5/kern/fs/swapfs.c create mode 100644 code/lab5/kern/fs/swapfs.h create mode 100644 code/lab5/kern/init/entry.S create mode 100644 code/lab5/kern/init/init.c create mode 100644 code/lab5/kern/libs/rb_tree.c create mode 100644 code/lab5/kern/libs/rb_tree.h create mode 100644 code/lab5/kern/libs/readline.c create mode 100644 code/lab5/kern/libs/stdio.c create mode 100644 code/lab5/kern/mm/default_pmm.c create mode 100644 code/lab5/kern/mm/default_pmm.h create mode 100644 code/lab5/kern/mm/kmalloc.c create mode 100644 code/lab5/kern/mm/kmalloc.h create mode 100644 code/lab5/kern/mm/memlayout.h create mode 100644 code/lab5/kern/mm/mmu.h create mode 100644 code/lab5/kern/mm/pmm.c create mode 100644 code/lab5/kern/mm/pmm.h create mode 100644 code/lab5/kern/mm/swap.c create mode 100644 code/lab5/kern/mm/swap.h create mode 100644 code/lab5/kern/mm/swap_fifo.c create mode 100644 code/lab5/kern/mm/swap_fifo.h create mode 100644 code/lab5/kern/mm/vmm.c create mode 100644 code/lab5/kern/mm/vmm.h create mode 100644 code/lab5/kern/process/entry.S create mode 100644 code/lab5/kern/process/proc.c create mode 100644 code/lab5/kern/process/proc.h create mode 100644 code/lab5/kern/process/switch.S create mode 100644 code/lab5/kern/schedule/sched.c create mode 100644 code/lab5/kern/schedule/sched.h create mode 100644 code/lab5/kern/sync/sync.h create mode 100644 code/lab5/kern/syscall/syscall.c create mode 100644 code/lab5/kern/syscall/syscall.h create mode 100644 code/lab5/kern/trap/trap.c create mode 100644 code/lab5/kern/trap/trap.h create mode 100644 code/lab5/kern/trap/trapentry.S create mode 100644 code/lab5/kern/trap/vectors.S create mode 100644 code/lab5/libs/atomic.h create mode 100644 code/lab5/libs/defs.h create mode 100644 code/lab5/libs/elf.h create mode 100644 code/lab5/libs/error.h create mode 100644 code/lab5/libs/hash.c create mode 100644 code/lab5/libs/list.h create mode 100644 code/lab5/libs/printfmt.c create mode 100644 code/lab5/libs/rand.c create mode 100644 code/lab5/libs/stdarg.h create mode 100644 code/lab5/libs/stdio.h create mode 100644 code/lab5/libs/stdlib.h create mode 100644 code/lab5/libs/string.c create mode 100644 code/lab5/libs/string.h create mode 100644 code/lab5/libs/unistd.h create mode 100644 code/lab5/libs/x86.h create mode 100644 code/lab5/tools/boot.ld create mode 100644 code/lab5/tools/function.mk create mode 100644 code/lab5/tools/gdbinit create mode 100644 code/lab5/tools/grade.sh create mode 100644 code/lab5/tools/kernel.ld create mode 100644 code/lab5/tools/sign.c create mode 100644 code/lab5/tools/user.ld create mode 100644 code/lab5/tools/vector.c create mode 100644 code/lab5/user/badarg.c create mode 100644 code/lab5/user/badsegment.c create mode 100644 code/lab5/user/divzero.c create mode 100644 code/lab5/user/exit.c create mode 100644 code/lab5/user/faultread.c create mode 100644 code/lab5/user/faultreadkernel.c create mode 100644 code/lab5/user/forktest.c create mode 100644 code/lab5/user/forktree.c create mode 100644 code/lab5/user/hello.c create mode 100644 code/lab5/user/libs/initcode.S create mode 100644 code/lab5/user/libs/panic.c create mode 100644 code/lab5/user/libs/stdio.c create mode 100644 code/lab5/user/libs/syscall.c create mode 100644 code/lab5/user/libs/syscall.h create mode 100644 code/lab5/user/libs/ulib.c create mode 100644 code/lab5/user/libs/ulib.h create mode 100644 code/lab5/user/libs/umain.c create mode 100644 code/lab5/user/pgdir.c create mode 100644 code/lab5/user/softint.c create mode 100644 code/lab5/user/spin.c create mode 100644 code/lab5/user/testbss.c create mode 100644 code/lab5/user/waitkill.c create mode 100644 code/lab5/user/yield.c create mode 100644 code/lab6/Makefile create mode 100644 code/lab6/boot/asm.h create mode 100644 code/lab6/boot/bootasm.S create mode 100644 code/lab6/boot/bootmain.c create mode 100644 code/lab6/kern/debug/assert.h create mode 100644 code/lab6/kern/debug/kdebug.c create mode 100644 code/lab6/kern/debug/kdebug.h create mode 100644 code/lab6/kern/debug/monitor.c create mode 100644 code/lab6/kern/debug/monitor.h create mode 100644 code/lab6/kern/debug/panic.c create mode 100644 code/lab6/kern/debug/stab.h create mode 100644 code/lab6/kern/driver/clock.c create mode 100644 code/lab6/kern/driver/clock.h create mode 100644 code/lab6/kern/driver/console.c create mode 100644 code/lab6/kern/driver/console.h create mode 100644 code/lab6/kern/driver/ide.c create mode 100644 code/lab6/kern/driver/ide.h create mode 100644 code/lab6/kern/driver/intr.c create mode 100644 code/lab6/kern/driver/intr.h create mode 100644 code/lab6/kern/driver/kbdreg.h create mode 100644 code/lab6/kern/driver/picirq.c create mode 100644 code/lab6/kern/driver/picirq.h create mode 100644 code/lab6/kern/fs/fs.h create mode 100644 code/lab6/kern/fs/swapfs.c create mode 100644 code/lab6/kern/fs/swapfs.h create mode 100644 code/lab6/kern/init/entry.S create mode 100644 code/lab6/kern/init/init.c create mode 100644 code/lab6/kern/libs/rb_tree.c create mode 100644 code/lab6/kern/libs/rb_tree.h create mode 100644 code/lab6/kern/libs/readline.c create mode 100644 code/lab6/kern/libs/stdio.c create mode 100644 code/lab6/kern/mm/default_pmm.c create mode 100644 code/lab6/kern/mm/default_pmm.h create mode 100644 code/lab6/kern/mm/kmalloc.c create mode 100644 code/lab6/kern/mm/kmalloc.h create mode 100644 code/lab6/kern/mm/memlayout.h create mode 100644 code/lab6/kern/mm/mmu.h create mode 100644 code/lab6/kern/mm/pmm.c create mode 100644 code/lab6/kern/mm/pmm.h create mode 100644 code/lab6/kern/mm/swap.c create mode 100644 code/lab6/kern/mm/swap.h create mode 100644 code/lab6/kern/mm/swap_fifo.c create mode 100644 code/lab6/kern/mm/swap_fifo.h create mode 100644 code/lab6/kern/mm/vmm.c create mode 100644 code/lab6/kern/mm/vmm.h create mode 100644 code/lab6/kern/process/entry.S create mode 100644 code/lab6/kern/process/proc.c create mode 100644 code/lab6/kern/process/proc.h create mode 100644 code/lab6/kern/process/switch.S create mode 100644 code/lab6/kern/schedule/default_sched.c create mode 100644 code/lab6/kern/schedule/default_sched.h create mode 100644 code/lab6/kern/schedule/default_sched_stride_c create mode 100644 code/lab6/kern/schedule/sched.c create mode 100644 code/lab6/kern/schedule/sched.h create mode 100644 code/lab6/kern/sync/sync.h create mode 100644 code/lab6/kern/syscall/syscall.c create mode 100644 code/lab6/kern/syscall/syscall.h create mode 100644 code/lab6/kern/trap/trap.c create mode 100644 code/lab6/kern/trap/trap.h create mode 100644 code/lab6/kern/trap/trapentry.S create mode 100644 code/lab6/kern/trap/vectors.S create mode 100644 code/lab6/libs/atomic.h create mode 100644 code/lab6/libs/defs.h create mode 100644 code/lab6/libs/elf.h create mode 100644 code/lab6/libs/error.h create mode 100644 code/lab6/libs/hash.c create mode 100644 code/lab6/libs/list.h create mode 100644 code/lab6/libs/printfmt.c create mode 100644 code/lab6/libs/rand.c create mode 100644 code/lab6/libs/skew_heap.h create mode 100644 code/lab6/libs/stdarg.h create mode 100644 code/lab6/libs/stdio.h create mode 100644 code/lab6/libs/stdlib.h create mode 100644 code/lab6/libs/string.c create mode 100644 code/lab6/libs/string.h create mode 100644 code/lab6/libs/unistd.h create mode 100644 code/lab6/libs/x86.h create mode 100644 code/lab6/tools/boot.ld create mode 100644 code/lab6/tools/function.mk create mode 100644 code/lab6/tools/gdbinit create mode 100644 code/lab6/tools/grade.sh create mode 100644 code/lab6/tools/kernel.ld create mode 100644 code/lab6/tools/sign.c create mode 100644 code/lab6/tools/user.ld create mode 100644 code/lab6/tools/vector.c create mode 100644 code/lab6/user/badarg.c create mode 100644 code/lab6/user/badsegment.c create mode 100644 code/lab6/user/divzero.c create mode 100644 code/lab6/user/exit.c create mode 100644 code/lab6/user/faultread.c create mode 100644 code/lab6/user/faultreadkernel.c create mode 100644 code/lab6/user/forktest.c create mode 100644 code/lab6/user/forktree.c create mode 100644 code/lab6/user/hello.c create mode 100644 code/lab6/user/libs/initcode.S create mode 100644 code/lab6/user/libs/panic.c create mode 100644 code/lab6/user/libs/stdio.c create mode 100644 code/lab6/user/libs/syscall.c create mode 100644 code/lab6/user/libs/syscall.h create mode 100644 code/lab6/user/libs/ulib.c create mode 100644 code/lab6/user/libs/ulib.h create mode 100644 code/lab6/user/libs/umain.c create mode 100644 code/lab6/user/matrix.c create mode 100644 code/lab6/user/pgdir.c create mode 100644 code/lab6/user/priority.c create mode 100644 code/lab6/user/softint.c create mode 100644 code/lab6/user/spin.c create mode 100644 code/lab6/user/testbss.c create mode 100644 code/lab6/user/waitkill.c create mode 100644 code/lab6/user/yield.c create mode 100644 code/lab7/Makefile create mode 100644 code/lab7/boot/asm.h create mode 100644 code/lab7/boot/bootasm.S create mode 100644 code/lab7/boot/bootmain.c create mode 100644 code/lab7/kern/debug/assert.h create mode 100644 code/lab7/kern/debug/kdebug.c create mode 100644 code/lab7/kern/debug/kdebug.h create mode 100644 code/lab7/kern/debug/monitor.c create mode 100644 code/lab7/kern/debug/monitor.h create mode 100644 code/lab7/kern/debug/panic.c create mode 100644 code/lab7/kern/debug/stab.h create mode 100644 code/lab7/kern/driver/clock.c create mode 100644 code/lab7/kern/driver/clock.h create mode 100644 code/lab7/kern/driver/console.c create mode 100644 code/lab7/kern/driver/console.h create mode 100644 code/lab7/kern/driver/ide.c create mode 100644 code/lab7/kern/driver/ide.h create mode 100644 code/lab7/kern/driver/intr.c create mode 100644 code/lab7/kern/driver/intr.h create mode 100644 code/lab7/kern/driver/kbdreg.h create mode 100644 code/lab7/kern/driver/picirq.c create mode 100644 code/lab7/kern/driver/picirq.h create mode 100644 code/lab7/kern/fs/fs.h create mode 100644 code/lab7/kern/fs/swapfs.c create mode 100644 code/lab7/kern/fs/swapfs.h create mode 100644 code/lab7/kern/init/entry.S create mode 100644 code/lab7/kern/init/init.c create mode 100644 code/lab7/kern/libs/rb_tree.c create mode 100644 code/lab7/kern/libs/rb_tree.h create mode 100644 code/lab7/kern/libs/readline.c create mode 100644 code/lab7/kern/libs/stdio.c create mode 100644 code/lab7/kern/mm/default_pmm.c create mode 100644 code/lab7/kern/mm/default_pmm.h create mode 100644 code/lab7/kern/mm/kmalloc.c create mode 100644 code/lab7/kern/mm/kmalloc.h create mode 100644 code/lab7/kern/mm/memlayout.h create mode 100644 code/lab7/kern/mm/mmu.h create mode 100644 code/lab7/kern/mm/pmm.c create mode 100644 code/lab7/kern/mm/pmm.h create mode 100644 code/lab7/kern/mm/swap.c create mode 100644 code/lab7/kern/mm/swap.h create mode 100644 code/lab7/kern/mm/swap_fifo.c create mode 100644 code/lab7/kern/mm/swap_fifo.h create mode 100644 code/lab7/kern/mm/vmm.c create mode 100644 code/lab7/kern/mm/vmm.h create mode 100644 code/lab7/kern/process/entry.S create mode 100644 code/lab7/kern/process/proc.c create mode 100644 code/lab7/kern/process/proc.h create mode 100644 code/lab7/kern/process/switch.S create mode 100644 code/lab7/kern/schedule/default_sched.c create mode 100644 code/lab7/kern/schedule/default_sched.h create mode 100644 code/lab7/kern/schedule/default_sched_stride_c create mode 100644 code/lab7/kern/schedule/sched.c create mode 100644 code/lab7/kern/schedule/sched.h create mode 100644 code/lab7/kern/sync/check_sync.c create mode 100644 code/lab7/kern/sync/monitor.c create mode 100644 code/lab7/kern/sync/monitor.h create mode 100644 code/lab7/kern/sync/sem.c create mode 100644 code/lab7/kern/sync/sem.h create mode 100644 code/lab7/kern/sync/sync.h create mode 100644 code/lab7/kern/sync/wait.c create mode 100644 code/lab7/kern/sync/wait.h create mode 100644 code/lab7/kern/syscall/syscall.c create mode 100644 code/lab7/kern/syscall/syscall.h create mode 100644 code/lab7/kern/trap/trap.c create mode 100644 code/lab7/kern/trap/trap.h create mode 100644 code/lab7/kern/trap/trapentry.S create mode 100644 code/lab7/kern/trap/vectors.S create mode 100644 code/lab7/libs/atomic.h create mode 100644 code/lab7/libs/defs.h create mode 100644 code/lab7/libs/elf.h create mode 100644 code/lab7/libs/error.h create mode 100644 code/lab7/libs/hash.c create mode 100644 code/lab7/libs/list.h create mode 100644 code/lab7/libs/printfmt.c create mode 100644 code/lab7/libs/rand.c create mode 100644 code/lab7/libs/skew_heap.h create mode 100644 code/lab7/libs/stdarg.h create mode 100644 code/lab7/libs/stdio.h create mode 100644 code/lab7/libs/stdlib.h create mode 100644 code/lab7/libs/string.c create mode 100644 code/lab7/libs/string.h create mode 100644 code/lab7/libs/unistd.h create mode 100644 code/lab7/libs/x86.h create mode 100644 code/lab7/tools/boot.ld create mode 100644 code/lab7/tools/function.mk create mode 100644 code/lab7/tools/gdbinit create mode 100644 code/lab7/tools/grade.sh create mode 100644 code/lab7/tools/kernel.ld create mode 100644 code/lab7/tools/sign.c create mode 100644 code/lab7/tools/user.ld create mode 100644 code/lab7/tools/vector.c create mode 100644 code/lab7/user/badarg.c create mode 100644 code/lab7/user/badsegment.c create mode 100644 code/lab7/user/divzero.c create mode 100644 code/lab7/user/exit.c create mode 100644 code/lab7/user/faultread.c create mode 100644 code/lab7/user/faultreadkernel.c create mode 100644 code/lab7/user/forktest.c create mode 100644 code/lab7/user/forktree.c create mode 100644 code/lab7/user/hello.c create mode 100644 code/lab7/user/libs/initcode.S create mode 100644 code/lab7/user/libs/panic.c create mode 100644 code/lab7/user/libs/stdio.c create mode 100644 code/lab7/user/libs/syscall.c create mode 100644 code/lab7/user/libs/syscall.h create mode 100644 code/lab7/user/libs/ulib.c create mode 100644 code/lab7/user/libs/ulib.h create mode 100644 code/lab7/user/libs/umain.c create mode 100644 code/lab7/user/matrix.c create mode 100644 code/lab7/user/pgdir.c create mode 100644 code/lab7/user/priority.c create mode 100644 code/lab7/user/sleep.c create mode 100644 code/lab7/user/sleepkill.c create mode 100644 code/lab7/user/softint.c create mode 100644 code/lab7/user/spin.c create mode 100644 code/lab7/user/testbss.c create mode 100644 code/lab7/user/waitkill.c create mode 100644 code/lab7/user/yield.c create mode 100644 code/lab8/Makefile create mode 100644 code/lab8/boot/asm.h create mode 100644 code/lab8/boot/bootasm.S create mode 100644 code/lab8/boot/bootmain.c create mode 100644 code/lab8/kern/debug/assert.h create mode 100644 code/lab8/kern/debug/kdebug.c create mode 100644 code/lab8/kern/debug/kdebug.h create mode 100644 code/lab8/kern/debug/monitor.c create mode 100644 code/lab8/kern/debug/monitor.h create mode 100644 code/lab8/kern/debug/panic.c create mode 100644 code/lab8/kern/debug/stab.h create mode 100644 code/lab8/kern/driver/clock.c create mode 100644 code/lab8/kern/driver/clock.h create mode 100644 code/lab8/kern/driver/console.c create mode 100644 code/lab8/kern/driver/console.h create mode 100644 code/lab8/kern/driver/ide.c create mode 100644 code/lab8/kern/driver/ide.h create mode 100644 code/lab8/kern/driver/intr.c create mode 100644 code/lab8/kern/driver/intr.h create mode 100644 code/lab8/kern/driver/kbdreg.h create mode 100644 code/lab8/kern/driver/picirq.c create mode 100644 code/lab8/kern/driver/picirq.h create mode 100644 code/lab8/kern/fs/devs/dev.c create mode 100644 code/lab8/kern/fs/devs/dev.h create mode 100644 code/lab8/kern/fs/devs/dev_disk0.c create mode 100644 code/lab8/kern/fs/devs/dev_stdin.c create mode 100644 code/lab8/kern/fs/devs/dev_stdout.c create mode 100644 code/lab8/kern/fs/file.c create mode 100644 code/lab8/kern/fs/file.h create mode 100644 code/lab8/kern/fs/fs.c create mode 100644 code/lab8/kern/fs/fs.h create mode 100644 code/lab8/kern/fs/iobuf.c create mode 100644 code/lab8/kern/fs/iobuf.h create mode 100644 code/lab8/kern/fs/sfs/bitmap.c create mode 100644 code/lab8/kern/fs/sfs/bitmap.h create mode 100644 code/lab8/kern/fs/sfs/sfs.c create mode 100644 code/lab8/kern/fs/sfs/sfs.h create mode 100644 code/lab8/kern/fs/sfs/sfs_fs.c create mode 100644 code/lab8/kern/fs/sfs/sfs_inode.c create mode 100644 code/lab8/kern/fs/sfs/sfs_io.c create mode 100644 code/lab8/kern/fs/sfs/sfs_lock.c create mode 100644 code/lab8/kern/fs/swap/swapfs.c create mode 100644 code/lab8/kern/fs/swap/swapfs.h create mode 100644 code/lab8/kern/fs/sysfile.c create mode 100644 code/lab8/kern/fs/sysfile.h create mode 100644 code/lab8/kern/fs/vfs/inode.c create mode 100644 code/lab8/kern/fs/vfs/inode.h create mode 100644 code/lab8/kern/fs/vfs/vfs.c create mode 100644 code/lab8/kern/fs/vfs/vfs.h create mode 100644 code/lab8/kern/fs/vfs/vfsdev.c create mode 100644 code/lab8/kern/fs/vfs/vfsfile.c create mode 100644 code/lab8/kern/fs/vfs/vfslookup.c create mode 100644 code/lab8/kern/fs/vfs/vfspath.c create mode 100644 code/lab8/kern/init/entry.S create mode 100644 code/lab8/kern/init/init.c create mode 100644 code/lab8/kern/libs/rb_tree.c create mode 100644 code/lab8/kern/libs/rb_tree.h create mode 100644 code/lab8/kern/libs/readline.c create mode 100644 code/lab8/kern/libs/stdio.c create mode 100644 code/lab8/kern/libs/string.c create mode 100644 code/lab8/kern/mm/default_pmm.c create mode 100644 code/lab8/kern/mm/default_pmm.h create mode 100644 code/lab8/kern/mm/kmalloc.c create mode 100644 code/lab8/kern/mm/kmalloc.h create mode 100644 code/lab8/kern/mm/memlayout.h create mode 100644 code/lab8/kern/mm/mmu.h create mode 100644 code/lab8/kern/mm/pmm.c create mode 100644 code/lab8/kern/mm/pmm.h create mode 100644 code/lab8/kern/mm/swap.c create mode 100644 code/lab8/kern/mm/swap.h create mode 100644 code/lab8/kern/mm/swap_fifo.c create mode 100644 code/lab8/kern/mm/swap_fifo.h create mode 100644 code/lab8/kern/mm/vmm.c create mode 100644 code/lab8/kern/mm/vmm.h create mode 100644 code/lab8/kern/process/entry.S create mode 100644 code/lab8/kern/process/proc.c create mode 100644 code/lab8/kern/process/proc.h create mode 100644 code/lab8/kern/process/switch.S create mode 100644 code/lab8/kern/schedule/default_sched.c create mode 100644 code/lab8/kern/schedule/default_sched.h create mode 100644 code/lab8/kern/schedule/default_sched_stride_c create mode 100644 code/lab8/kern/schedule/sched.c create mode 100644 code/lab8/kern/schedule/sched.h create mode 100644 code/lab8/kern/sync/check_sync.c create mode 100644 code/lab8/kern/sync/monitor.c create mode 100644 code/lab8/kern/sync/monitor.h create mode 100644 code/lab8/kern/sync/sem.c create mode 100644 code/lab8/kern/sync/sem.h create mode 100644 code/lab8/kern/sync/sync.h create mode 100644 code/lab8/kern/sync/wait.c create mode 100644 code/lab8/kern/sync/wait.h create mode 100644 code/lab8/kern/syscall/syscall.c create mode 100644 code/lab8/kern/syscall/syscall.h create mode 100644 code/lab8/kern/trap/trap.c create mode 100644 code/lab8/kern/trap/trap.h create mode 100644 code/lab8/kern/trap/trapentry.S create mode 100644 code/lab8/kern/trap/vectors.S create mode 100644 code/lab8/libs/atomic.h create mode 100644 code/lab8/libs/defs.h create mode 100644 code/lab8/libs/dirent.h create mode 100644 code/lab8/libs/elf.h create mode 100644 code/lab8/libs/error.h create mode 100644 code/lab8/libs/hash.c create mode 100644 code/lab8/libs/list.h create mode 100644 code/lab8/libs/printfmt.c create mode 100644 code/lab8/libs/rand.c create mode 100644 code/lab8/libs/skew_heap.h create mode 100644 code/lab8/libs/stat.h create mode 100644 code/lab8/libs/stdarg.h create mode 100644 code/lab8/libs/stdio.h create mode 100644 code/lab8/libs/stdlib.h create mode 100644 code/lab8/libs/string.c create mode 100644 code/lab8/libs/string.h create mode 100644 code/lab8/libs/unistd.h create mode 100644 code/lab8/libs/x86.h create mode 100644 code/lab8/tools/boot.ld create mode 100644 code/lab8/tools/function.mk create mode 100644 code/lab8/tools/gdbinit create mode 100644 code/lab8/tools/grade.sh create mode 100644 code/lab8/tools/kernel.ld create mode 100644 code/lab8/tools/mksfs.c create mode 100644 code/lab8/tools/sign.c create mode 100644 code/lab8/tools/user.ld create mode 100644 code/lab8/tools/vector.c create mode 100644 code/lab8/user/badarg.c create mode 100644 code/lab8/user/badsegment.c create mode 100644 code/lab8/user/divzero.c create mode 100644 code/lab8/user/exit.c create mode 100644 code/lab8/user/faultread.c create mode 100644 code/lab8/user/faultreadkernel.c create mode 100644 code/lab8/user/forktest.c create mode 100644 code/lab8/user/forktree.c create mode 100644 code/lab8/user/hello.c create mode 100644 code/lab8/user/libs/dir.c create mode 100644 code/lab8/user/libs/dir.h create mode 100644 code/lab8/user/libs/file.c create mode 100644 code/lab8/user/libs/file.h create mode 100644 code/lab8/user/libs/initcode.S create mode 100644 code/lab8/user/libs/lock.h create mode 100644 code/lab8/user/libs/panic.c create mode 100644 code/lab8/user/libs/stdio.c create mode 100644 code/lab8/user/libs/syscall.c create mode 100644 code/lab8/user/libs/syscall.h create mode 100644 code/lab8/user/libs/ulib.c create mode 100644 code/lab8/user/libs/ulib.h create mode 100644 code/lab8/user/libs/umain.c create mode 100644 code/lab8/user/ls.c create mode 100644 code/lab8/user/matrix.c create mode 100644 code/lab8/user/pgdir.c create mode 100644 code/lab8/user/priority.c create mode 100644 code/lab8/user/sh.c create mode 100644 code/lab8/user/sleep.c create mode 100644 code/lab8/user/sleepkill.c create mode 100644 code/lab8/user/softint.c create mode 100644 code/lab8/user/spin.c create mode 100644 code/lab8/user/testbss.c create mode 100644 code/lab8/user/waitkill.c create mode 100644 code/lab8/user/yield.c create mode 100644 doc/lab0.pdf create mode 100644 doc/lab1.pdf create mode 100644 doc/lab2.pdf create mode 100644 doc/lab3.pdf create mode 100644 doc/lab4.pdf create mode 100644 doc/lab5.pdf create mode 100644 doc/lab6.pdf create mode 100644 doc/lab7.pdf create mode 100644 doc/lab8.pdf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..39dca48 --- /dev/null +++ b/LICENSE @@ -0,0 +1,388 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. +-------------------------------------------------------------------------------------------- +Copyright (c) 2006-2007 Frans Kaashoek, Robert Morris, and Russ Cox + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +-------------------------------------------------------------------------------------------- +Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009 + The President and Fellows of Harvard College. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/code/lab1/Makefile b/code/lab1/Makefile new file mode 100644 index 0000000..a828239 --- /dev/null +++ b/code/lab1/Makefile @@ -0,0 +1,250 @@ +PROJ := challenge +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-elf-qemu > /dev/null; \ + then echo 'i386-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +CC := $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +.PHONY: qemu qemu-nox debug debug-nox +qemu: $(UCOREIMG) + $(V)$(QEMU) -parallel stdio -hda $< -serial null + +qemu-nox: $(UCOREIMG) + $(V)$(QEMU) -serial mon:stdio -hda $< -nographic +TERMINAL :=gnome-terminal +debug: $(UCOREIMG) + $(V)$(QEMU) -S -s -parallel stdio -hda $< -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "gdb -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) + $(V)$(QEMU) -S -s -serial mon:stdio -hda $< -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "gdb -q -x tools/gdbinit" + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := proj$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/trap/trap.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab1/boot/asm.h b/code/lab1/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab1/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab1/boot/bootasm.S b/code/lab1/boot/bootasm.S new file mode 100644 index 0000000..35647f5 --- /dev/null +++ b/code/lab1/boot/bootasm.S @@ -0,0 +1,89 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab1/boot/bootmain.c b/code/lab1/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab1/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab1/kern/debug/assert.h b/code/lab1/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab1/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab1/kern/debug/kdebug.c b/code/lab1/kern/debug/kdebug.c new file mode 100644 index 0000000..27dded0 --- /dev/null +++ b/code/lab1/kern/debug/kdebug.c @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab1/kern/debug/kdebug.h b/code/lab1/kern/debug/kdebug.h new file mode 100644 index 0000000..5f4d2c8 --- /dev/null +++ b/code/lab1/kern/debug/kdebug.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab1/kern/debug/monitor.c b/code/lab1/kern/debug/monitor.c new file mode 100644 index 0000000..644b3a6 --- /dev/null +++ b/code/lab1/kern/debug/monitor.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab1/kern/debug/monitor.h b/code/lab1/kern/debug/monitor.h new file mode 100644 index 0000000..afc3f5d --- /dev/null +++ b/code/lab1/kern/debug/monitor.h @@ -0,0 +1,13 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab1/kern/debug/panic.c b/code/lab1/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab1/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab1/kern/debug/stab.h b/code/lab1/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab1/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab1/kern/driver/clock.c b/code/lab1/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab1/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab1/kern/driver/clock.h b/code/lab1/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab1/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab1/kern/driver/console.c b/code/lab1/kern/driver/console.c new file mode 100644 index 0000000..8ab42b8 --- /dev/null +++ b/code/lab1/kern/driver/console.c @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)CGA_BUF; + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)MONO_BUF; + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + lpt_putc(c); + cga_putc(c); + serial_putc(c); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c; + + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + return c; + } + return 0; +} + diff --git a/code/lab1/kern/driver/console.h b/code/lab1/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab1/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab1/kern/driver/intr.c b/code/lab1/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab1/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab1/kern/driver/intr.h b/code/lab1/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab1/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab1/kern/driver/kbdreg.h b/code/lab1/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab1/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab1/kern/driver/picirq.c b/code/lab1/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab1/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab1/kern/driver/picirq.h b/code/lab1/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab1/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab1/kern/init/init.c b/code/lab1/kern/init/init.c new file mode 100644 index 0000000..a1f3595 --- /dev/null +++ b/code/lab1/kern/init/init.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + /* do nothing */ + while (1); +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab1/kern/libs/readline.c b/code/lab1/kern/libs/readline.c new file mode 100644 index 0000000..67489e3 --- /dev/null +++ b/code/lab1/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab1/kern/libs/stdio.c b/code/lab1/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab1/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab1/kern/mm/memlayout.h b/code/lab1/kern/mm/memlayout.h new file mode 100644 index 0000000..2d8455d --- /dev/null +++ b/code/lab1/kern/mm/memlayout.h @@ -0,0 +1,29 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab1/kern/mm/mmu.h b/code/lab1/kern/mm/mmu.h new file mode 100644 index 0000000..4ff779c --- /dev/null +++ b/code/lab1/kern/mm/mmu.h @@ -0,0 +1,174 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ +} + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ +} + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc){0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc){ \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEG16(type, base, lim, dpl) \ + (struct segdesc){ \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +}; + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab1/kern/mm/pmm.c b/code/lab1/kern/mm/pmm.c new file mode 100644 index 0000000..238cd14 --- /dev/null +++ b/code/lab1/kern/mm/pmm.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uint32_t)gdt +}; + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* temporary kernel stack */ +uint8_t stack0[1024]; + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // Setup a TSS so that we can get the right stack when we trap from + // user to the kernel. But not safe here, it's only a temporary value, + // it will be set to KSTACKTOP in lab2. + ts.ts_esp0 = (uint32_t)&stack0 + sizeof(stack0); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEG16(STS_T32A, (uint32_t)&ts, sizeof(ts), DPL_KERNEL); + gdt[SEG_TSS].sd_s = 0; + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +/* pmm_init - initialize the physical memory management */ +void +pmm_init(void) { + gdt_init(); +} + diff --git a/code/lab1/kern/mm/pmm.h b/code/lab1/kern/mm/pmm.h new file mode 100644 index 0000000..9b2898e --- /dev/null +++ b/code/lab1/kern/mm/pmm.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +void pmm_init(void); + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab1/kern/trap/trap.c b/code/lab1/kern/trap/trap.c new file mode 100644 index 0000000..af78676 --- /dev/null +++ b/code/lab1/kern/trap/trap.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +/* trap_dispatch - dispatch based on what type of trap occurred */ +static void +trap_dispatch(struct trapframe *tf) { + char c; + + switch (tf->tf_trapno) { + case IRQ_OFFSET + IRQ_TIMER: + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + // in kernel, it must be a mistake + if ((tf->tf_cs & 3) == 0) { + print_trapframe(tf); + panic("unexpected trap in kernel.\n"); + } + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + trap_dispatch(tf); +} + diff --git a/code/lab1/kern/trap/trap.h b/code/lab1/kern/trap/trap.h new file mode 100644 index 0000000..d0fb0b3 --- /dev/null +++ b/code/lab1/kern/trap/trap.h @@ -0,0 +1,91 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +#define T_SYSCALL 0x80 // SYSCALL, ONLY FOR THIS PROJ + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab1/kern/trap/trapentry.S b/code/lab1/kern/trap/trapentry.S new file mode 100644 index 0000000..55e29b7 --- /dev/null +++ b/code/lab1/kern/trap/trapentry.S @@ -0,0 +1,44 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + diff --git a/code/lab1/kern/trap/vectors.S b/code/lab1/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab1/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab1/libs/defs.h b/code/lab1/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab1/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab1/libs/elf.h b/code/lab1/libs/elf.h new file mode 100644 index 0000000..29f7061 --- /dev/null +++ b/code/lab1/libs/elf.h @@ -0,0 +1,40 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab1/libs/error.h b/code/lab1/libs/error.h new file mode 100644 index 0000000..bafc169 --- /dev/null +++ b/code/lab1/libs/error.h @@ -0,0 +1,16 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault + +/* the maximum allowed */ +#define MAXERROR 6 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab1/libs/printfmt.c b/code/lab1/libs/printfmt.c new file mode 100644 index 0000000..2a7f40b --- /dev/null +++ b/code/lab1/libs/printfmt.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab1/libs/stdarg.h b/code/lab1/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab1/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab1/libs/stdio.h b/code/lab1/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab1/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab1/libs/string.c b/code/lab1/libs/string.c new file mode 100644 index 0000000..c3d88e6 --- /dev/null +++ b/code/lab1/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab1/libs/string.h b/code/lab1/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab1/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab1/libs/x86.h b/code/lab1/libs/x86.h new file mode 100644 index 0000000..319e8c0 --- /dev/null +++ b/code/lab1/libs/x86.h @@ -0,0 +1,191 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uint32_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port)); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port)); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd)); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel)); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab1/tools/function.mk b/code/lab1/tools/function.mk new file mode 100644 index 0000000..58a27e4 --- /dev/null +++ b/code/lab1/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR) $(OBJDIR)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab1/tools/gdbinit b/code/lab1/tools/gdbinit new file mode 100644 index 0000000..abf6161 --- /dev/null +++ b/code/lab1/tools/gdbinit @@ -0,0 +1,4 @@ +file bin/kernel +target remote :1234 +break kern_init +continue \ No newline at end of file diff --git a/code/lab1/tools/grade.sh b/code/lab1/tools/grade.sh new file mode 100644 index 0000000..f90d1a1 --- /dev/null +++ b/code/lab1/tools/grade.sh @@ -0,0 +1,348 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GCCPREFIX)gdb" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-Ddef...] [-check ] checkargs ... + tag= + check=check_regexps + while true; do + select= + case $1 in + -tag) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## set default qemu-options +qemuopts="-hda $osimg" + +## set break-function, default is readline +brkfun=readline + +## check now!! + +quick_run 'Check Output' + +pts=10 +quick_check 'check ring 0' \ + '0: @ring 0' \ + '0: cs = 8' \ + '0: ds = 10' \ + '0: es = 10' \ + '0: ss = 10' + +quick_check 'check switch to ring 3' \ + '+++ switch to user mode +++' \ + '1: @ring 3' \ + '1: cs = 1b' \ + '1: ds = 23' \ + '1: es = 23' \ + '1: ss = 23' + +quick_check 'check switch to ring 0' \ + '+++ switch to kernel mode +++' \ + '2: @ring 0' \ + '2: cs = 8' \ + '2: ds = 10' \ + '2: es = 10' \ + '2: ss = 10' + +quick_check 'check ticks' \ + '++ setup timer interrupts' \ + '100 ticks' \ + 'End of Test.' + +## print final-score +show_final + diff --git a/code/lab1/tools/kernel.ld b/code/lab1/tools/kernel.ld new file mode 100644 index 0000000..2bdb6e4 --- /dev/null +++ b/code/lab1/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the JOS kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_init) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0x100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab1/tools/sign.c b/code/lab1/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab1/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab1/tools/vector.c b/code/lab1/tools/vector.c new file mode 100644 index 0000000..df993cf --- /dev/null +++ b/code/lab1/tools/vector.c @@ -0,0 +1,28 @@ +#include + +int +main(void) { + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl \\$0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab2/Makefile b/code/lab2/Makefile new file mode 100644 index 0000000..a10592c --- /dev/null +++ b/code/lab2/Makefile @@ -0,0 +1,261 @@ +PROJ := 5 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +GDB := $(GCCPREFIX)gdb + +CC := $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +TAR := tar +ZIP := gzip + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = gdb \ + clean \ + distclean \ + grade \ + touch \ + print-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +targets: $(TARGETS) + +.DEFAULT_GOAL := targets + +QEMUOPTS = -hda $(UCOREIMG) + +.PHONY: qemu qemu-nox gdb debug debug-mon debug-nox +qemu: targets + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: targets + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +gdb: + $(V)$(GDB) -q -x tools/gdbinit + +debug: targets + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null + +debug-mon: targets + $(V)$(QEMU) -S -s -monitor stdio $(QEMUOPTS) -parallel null -serial null + +debug-nox: targets + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := lab2-handin.tar.gz + +TOUCH_FILES := kern/trap/trap.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean distclean handin +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + $(V)$(RM) -r $(OBJDIR) $(BINDIR) + +distclean: clean + $(V)$(RM) $(HANDIN) + +handin: distclean + $(V)$(TAR) -cf - `find . -type f -o -type d | grep -v '^\.$$' | grep -v '/CVS/' \ + | grep -v '/\.git/' | grep -v '/\.svn/' | grep -v "$(HANDIN)"` \ + | $(ZIP) > $(HANDIN) + diff --git a/code/lab2/boot/asm.h b/code/lab2/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab2/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab2/boot/bootasm.S b/code/lab2/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab2/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab2/boot/bootmain.c b/code/lab2/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab2/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab2/kern/debug/assert.h b/code/lab2/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab2/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab2/kern/debug/kdebug.c b/code/lab2/kern/debug/kdebug.c new file mode 100644 index 0000000..fbf7346 --- /dev/null +++ b/code/lab2/kern/debug/kdebug.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab2/kern/debug/kdebug.h b/code/lab2/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab2/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab2/kern/debug/monitor.c b/code/lab2/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab2/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab2/kern/debug/monitor.h b/code/lab2/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab2/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab2/kern/debug/panic.c b/code/lab2/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab2/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab2/kern/debug/stab.h b/code/lab2/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab2/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab2/kern/driver/clock.c b/code/lab2/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab2/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab2/kern/driver/clock.h b/code/lab2/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab2/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab2/kern/driver/console.c b/code/lab2/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab2/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab2/kern/driver/console.h b/code/lab2/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab2/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab2/kern/driver/intr.c b/code/lab2/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab2/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab2/kern/driver/intr.h b/code/lab2/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab2/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab2/kern/driver/kbdreg.h b/code/lab2/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab2/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab2/kern/driver/picirq.c b/code/lab2/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab2/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab2/kern/driver/picirq.h b/code/lab2/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab2/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab2/kern/init/entry.S b/code/lab2/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab2/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab2/kern/init/init.c b/code/lab2/kern/init/init.c new file mode 100644 index 0000000..a1f3595 --- /dev/null +++ b/code/lab2/kern/init/init.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + /* do nothing */ + while (1); +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab2/kern/libs/readline.c b/code/lab2/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab2/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab2/kern/libs/stdio.c b/code/lab2/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab2/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab2/kern/mm/default_pmm.c b/code/lab2/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab2/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab2/kern/mm/default_pmm.h b/code/lab2/kern/mm/default_pmm.h new file mode 100644 index 0000000..07352aa --- /dev/null +++ b/code/lab2/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; + +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab2/kern/mm/memlayout.h b/code/lab2/kern/mm/memlayout.h new file mode 100644 index 0000000..8b18fe5 --- /dev/null +++ b/code/lab2/kern/mm/memlayout.h @@ -0,0 +1,130 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | | + * | | + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // the num of free block, used in first fit pm manager + list_entry_t page_link; // free list link +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab2/kern/mm/mmu.h b/code/lab2/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab2/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab2/kern/mm/pmm.c b/code/lab2/kern/mm/pmm.c new file mode 100644 index 0000000..9e4f338 --- /dev/null +++ b/code/lab2/kern/mm/pmm.c @@ -0,0 +1,627 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} + diff --git a/code/lab2/kern/mm/pmm.h b/code/lab2/kern/mm/pmm.h new file mode 100644 index 0000000..54d573e --- /dev/null +++ b/code/lab2/kern/mm/pmm.h @@ -0,0 +1,141 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab2/kern/sync/sync.h b/code/lab2/kern/sync/sync.h new file mode 100644 index 0000000..9653377 --- /dev/null +++ b/code/lab2/kern/sync/sync.h @@ -0,0 +1,28 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab2/kern/trap/trap.c b/code/lab2/kern/trap/trap.c new file mode 100644 index 0000000..af78676 --- /dev/null +++ b/code/lab2/kern/trap/trap.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +/* trap_dispatch - dispatch based on what type of trap occurred */ +static void +trap_dispatch(struct trapframe *tf) { + char c; + + switch (tf->tf_trapno) { + case IRQ_OFFSET + IRQ_TIMER: + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + // in kernel, it must be a mistake + if ((tf->tf_cs & 3) == 0) { + print_trapframe(tf); + panic("unexpected trap in kernel.\n"); + } + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + trap_dispatch(tf); +} + diff --git a/code/lab2/kern/trap/trap.h b/code/lab2/kern/trap/trap.h new file mode 100644 index 0000000..74d973d --- /dev/null +++ b/code/lab2/kern/trap/trap.h @@ -0,0 +1,91 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +#define T_SYSCALL 0x80 // SYSCALL, ONLY FOR THIS PROJ + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab2/kern/trap/trapentry.S b/code/lab2/kern/trap/trapentry.S new file mode 100644 index 0000000..55e29b7 --- /dev/null +++ b/code/lab2/kern/trap/trapentry.S @@ -0,0 +1,44 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + diff --git a/code/lab2/kern/trap/vectors.S b/code/lab2/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab2/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab2/libs/atomic.h b/code/lab2/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab2/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab2/libs/defs.h b/code/lab2/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab2/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab2/libs/elf.h b/code/lab2/libs/elf.h new file mode 100644 index 0000000..bdfee3d --- /dev/null +++ b/code/lab2/libs/elf.h @@ -0,0 +1,40 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab2/libs/error.h b/code/lab2/libs/error.h new file mode 100644 index 0000000..b43fbd6 --- /dev/null +++ b/code/lab2/libs/error.h @@ -0,0 +1,16 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault + +/* the maximum allowed */ +#define MAXERROR 6 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab2/libs/list.h b/code/lab2/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab2/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab2/libs/printfmt.c b/code/lab2/libs/printfmt.c new file mode 100644 index 0000000..93a1c47 --- /dev/null +++ b/code/lab2/libs/printfmt.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab2/libs/stdarg.h b/code/lab2/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab2/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab2/libs/stdio.h b/code/lab2/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab2/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab2/libs/string.c b/code/lab2/libs/string.c new file mode 100644 index 0000000..bcf1b1c --- /dev/null +++ b/code/lab2/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab2/libs/string.h b/code/lab2/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab2/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab2/libs/x86.h b/code/lab2/libs/x86.h new file mode 100644 index 0000000..b29f671 --- /dev/null +++ b/code/lab2/libs/x86.h @@ -0,0 +1,302 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab2/tools/boot.ld b/code/lab2/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab2/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab2/tools/function.mk b/code/lab2/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab2/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab2/tools/gdbinit b/code/lab2/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab2/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab2/tools/grade.sh b/code/lab2/tools/grade.sh new file mode 100644 index 0000000..483b385 --- /dev/null +++ b/code/lab2/tools/grade.sh @@ -0,0 +1,340 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-Ddef...] [-check ] checkargs ... + tag= + check=check_regexps + while true; do + select= + case $1 in + -tag) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## set default qemu-options +qemuopts="-hda $osimg" + +## set break-function, default is readline +brkfun=readline + +## check now!! + +quick_run 'Check PMM' + +pts=20 +quick_check 'check pmm' \ + 'memory management: default_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' + +pts=20 +quick_check 'check page table' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' + +pts=10 +quick_check 'check ticks' \ + '++ setup timer interrupts' \ + '100 ticks' \ + 'End of Test.' + +## print final-score +show_final + diff --git a/code/lab2/tools/kernel.ld b/code/lab2/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab2/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab2/tools/sign.c b/code/lab2/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab2/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab2/tools/vector.c b/code/lab2/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab2/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab3/Makefile b/code/lab3/Makefile new file mode 100644 index 0000000..89583cf --- /dev/null +++ b/code/lab3/Makefile @@ -0,0 +1,271 @@ +PROJ := 8 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +GDB := $(GCCPREFIX)gdb + +CC ?= $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ \ + kern/fs/ + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync \ + kern/fs + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# ------------------------------------------------------------------- + +# create swap.img +SWAPIMG := $(call totarget,swap.img) + +$(SWAPIMG): + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + +$(call create_target,swap.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback + +.PHONY: qemu qemu-nox debug debug-nox +qemu: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +TERMINAL := gnome-terminal + +debug: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := proj$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/trap/trap.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab3/boot/asm.h b/code/lab3/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab3/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab3/boot/bootasm.S b/code/lab3/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab3/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab3/boot/bootmain.c b/code/lab3/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab3/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab3/kern/debug/assert.h b/code/lab3/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab3/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab3/kern/debug/kdebug.c b/code/lab3/kern/debug/kdebug.c new file mode 100644 index 0000000..fbf7346 --- /dev/null +++ b/code/lab3/kern/debug/kdebug.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab3/kern/debug/kdebug.h b/code/lab3/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab3/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab3/kern/debug/monitor.c b/code/lab3/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab3/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab3/kern/debug/monitor.h b/code/lab3/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab3/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab3/kern/debug/panic.c b/code/lab3/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab3/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab3/kern/debug/stab.h b/code/lab3/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab3/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab3/kern/driver/clock.c b/code/lab3/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab3/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab3/kern/driver/clock.h b/code/lab3/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab3/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab3/kern/driver/console.c b/code/lab3/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab3/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab3/kern/driver/console.h b/code/lab3/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab3/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab3/kern/driver/ide.c b/code/lab3/kern/driver/ide.c new file mode 100644 index 0000000..271cfa8 --- /dev/null +++ b/code/lab3/kern/driver/ide.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA_DATA 0x00 +#define ISA_ERROR 0x01 +#define ISA_PRECOMP 0x01 +#define ISA_CTRL 0x02 +#define ISA_SECCNT 0x02 +#define ISA_SECTOR 0x03 +#define ISA_CYL_LO 0x04 +#define ISA_CYL_HI 0x05 +#define ISA_SDH 0x06 +#define ISA_COMMAND 0x07 +#define ISA_STATUS 0x07 + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_DRQ 0x08 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_IDENTIFY 0xEC + +#define IDE_IDENT_SECTORS 20 +#define IDE_IDENT_MODEL 54 +#define IDE_IDENT_CAPABILITIES 98 +#define IDE_IDENT_CMDSETS 164 +#define IDE_IDENT_MAX_LBA 120 +#define IDE_IDENT_MAX_LBA_EXT 200 + +#define IO_BASE0 0x1F0 +#define IO_BASE1 0x170 +#define IO_CTRL0 0x3F4 +#define IO_CTRL1 0x374 + +#define MAX_IDE 4 +#define MAX_NSECS 128 +#define MAX_DISK_NSECS 0x10000000U +#define VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) + +static const struct { + unsigned short base; // I/O Base + unsigned short ctrl; // Control Base +} channels[2] = { + {IO_BASE0, IO_CTRL0}, + {IO_BASE1, IO_CTRL1}, +}; + +#define IO_BASE(ideno) (channels[(ideno) >> 1].base) +#define IO_CTRL(ideno) (channels[(ideno) >> 1].ctrl) + +static struct ide_device { + unsigned char valid; // 0 or 1 (If Device Really Exists) + unsigned int sets; // Commend Sets Supported + unsigned int size; // Size in Sectors + unsigned char model[41]; // Model in String +} ide_devices[MAX_IDE]; + +static int +ide_wait_ready(unsigned short iobase, bool check_error) { + int r; + while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY) + /* nothing */; + if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) { + return -1; + } + return 0; +} + +void +ide_init(void) { + static_assert((SECTSIZE % 4) == 0); + unsigned short ideno, iobase; + for (ideno = 0; ideno < MAX_IDE; ideno ++) { + /* assume that no device here */ + ide_devices[ideno].valid = 0; + + iobase = IO_BASE(ideno); + + /* wait device ready */ + ide_wait_ready(iobase, 0); + + /* step1: select drive */ + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4)); + ide_wait_ready(iobase, 0); + + /* step2: send ATA identify command */ + outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); + ide_wait_ready(iobase, 0); + + /* step3: polling */ + if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) { + continue ; + } + + /* device is ok */ + ide_devices[ideno].valid = 1; + + /* read identification space of the device */ + unsigned int buffer[128]; + insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); + + unsigned char *ident = (unsigned char *)buffer; + unsigned int sectors; + unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); + /* device use 48-bits or 28-bits addressing */ + if (cmdsets & (1 << 26)) { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); + } + else { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); + } + ide_devices[ideno].sets = cmdsets; + ide_devices[ideno].size = sectors; + + /* check if supports LBA */ + assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); + + unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; + unsigned int i, length = 40; + for (i = 0; i < length; i += 2) { + model[i] = data[i + 1], model[i + 1] = data[i]; + } + do { + model[i] = '\0'; + } while (i -- > 0 && model[i] == ' '); + + cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model); + } + + // enable ide interrupt + pic_enable(IRQ_IDE1); + pic_enable(IRQ_IDE2); +} + +bool +ide_device_valid(unsigned short ideno) { + return VALID_IDE(ideno); +} + +size_t +ide_device_size(unsigned short ideno) { + if (ide_device_valid(ideno)) { + return ide_devices[ideno].size; + } + return 0; +} + +int +ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_READ); + + int ret = 0; + for (; nsecs > 0; nsecs --, dst += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + insl(iobase, dst, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + +int +ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); + + int ret = 0; + for (; nsecs > 0; nsecs --, src += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + outsl(iobase, src, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + diff --git a/code/lab3/kern/driver/ide.h b/code/lab3/kern/driver/ide.h new file mode 100644 index 0000000..3e3fd21 --- /dev/null +++ b/code/lab3/kern/driver/ide.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_IDE_H__ +#define __KERN_DRIVER_IDE_H__ + +#include + +void ide_init(void); +bool ide_device_valid(unsigned short ideno); +size_t ide_device_size(unsigned short ideno); + +int ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs); +int ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs); + +#endif /* !__KERN_DRIVER_IDE_H__ */ + diff --git a/code/lab3/kern/driver/intr.c b/code/lab3/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab3/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab3/kern/driver/intr.h b/code/lab3/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab3/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab3/kern/driver/kbdreg.h b/code/lab3/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab3/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab3/kern/driver/picirq.c b/code/lab3/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab3/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab3/kern/driver/picirq.h b/code/lab3/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab3/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab3/kern/fs/fs.h b/code/lab3/kern/fs/fs.h new file mode 100644 index 0000000..92c05e7 --- /dev/null +++ b/code/lab3/kern/fs/fs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_FS_H__ +#define __KERN_FS_FS_H__ + +#include + +#define SECTSIZE 512 +#define PAGE_NSECT (PGSIZE / SECTSIZE) + +#define SWAP_DEV_NO 1 + +#endif /* !__KERN_FS_FS_H__ */ + diff --git a/code/lab3/kern/fs/swapfs.c b/code/lab3/kern/fs/swapfs.c new file mode 100644 index 0000000..d9f6090 --- /dev/null +++ b/code/lab3/kern/fs/swapfs.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include + +void +swapfs_init(void) { + static_assert((PGSIZE % SECTSIZE) == 0); + if (!ide_device_valid(SWAP_DEV_NO)) { + panic("swap fs isn't available.\n"); + } + max_swap_offset = ide_device_size(SWAP_DEV_NO) / (PGSIZE / SECTSIZE); +} + +int +swapfs_read(swap_entry_t entry, struct Page *page) { + return ide_read_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + +int +swapfs_write(swap_entry_t entry, struct Page *page) { + return ide_write_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + diff --git a/code/lab3/kern/fs/swapfs.h b/code/lab3/kern/fs/swapfs.h new file mode 100644 index 0000000..d433926 --- /dev/null +++ b/code/lab3/kern/fs/swapfs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_SWAPFS_H__ +#define __KERN_FS_SWAPFS_H__ + +#include +#include + +void swapfs_init(void); +int swapfs_read(swap_entry_t entry, struct Page *page); +int swapfs_write(swap_entry_t entry, struct Page *page); + +#endif /* !__KERN_FS_SWAPFS_H__ */ + diff --git a/code/lab3/kern/init/entry.S b/code/lab3/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab3/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab3/kern/init/init.c b/code/lab3/kern/init/init.c new file mode 100644 index 0000000..3564c96 --- /dev/null +++ b/code/lab3/kern/init/init.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + vmm_init(); // init virtual memory management + + ide_init(); // init ide devices + swap_init(); // init swap + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + /* do nothing */ + while (1); +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab3/kern/libs/readline.c b/code/lab3/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab3/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab3/kern/libs/stdio.c b/code/lab3/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab3/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab3/kern/mm/default_pmm.c b/code/lab3/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab3/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab3/kern/mm/default_pmm.h b/code/lab3/kern/mm/default_pmm.h new file mode 100644 index 0000000..07352aa --- /dev/null +++ b/code/lab3/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; + +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab3/kern/mm/memlayout.h b/code/lab3/kern/mm/memlayout.h new file mode 100644 index 0000000..af4f82e --- /dev/null +++ b/code/lab3/kern/mm/memlayout.h @@ -0,0 +1,133 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | | + * | | + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; +typedef pte_t swap_entry_t; //the pte can also be a swap entry + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // the num of free block, used in first fit pm manager + list_entry_t page_link; // free list link + list_entry_t pra_page_link; // used for pra (page replace algorithm) + uintptr_t pra_vaddr; // used for pra (page replace algorithm) +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab3/kern/mm/mmu.h b/code/lab3/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab3/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab3/kern/mm/pmm.c b/code/lab3/kern/mm/pmm.c new file mode 100644 index 0000000..3b63deb --- /dev/null +++ b/code/lab3/kern/mm/pmm.c @@ -0,0 +1,684 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + + while (1) + { + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + + if (page != NULL || n > 1 || swap_init_ok == 0) break; + + extern struct mm_struct *check_mm_struct; + //cprintf("page %x, call swap_out in alloc_pages %d\n",page, n); + swap_out(check_mm_struct, n, 0); + } + //cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages)); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +// pgdir_alloc_page - call alloc_page & page_insert functions to +// - allocate a page size memory & setup an addr map +// - pa<->la with linear address la and the PDT pgdir +struct Page * +pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { + struct Page *page = alloc_page(); + if (page != NULL) { + if (page_insert(pgdir, page, la, perm) != 0) { + free_page(page); + return NULL; + } + if (swap_init_ok){ + swap_map_swappable(check_mm_struct, la, page, 0); + page->pra_vaddr=la; + assert(page_ref(page) == 1); + //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); + } + + } + + return page; +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} + +void * +kmalloc(size_t n) { + void * ptr=NULL; + struct Page *base=NULL; + assert(n > 0 && n < 1024*0124); + int num_pages=(n+PGSIZE-1)/PGSIZE; + base = alloc_pages(num_pages); + assert(base != NULL); + ptr=page2kva(base); + return ptr; +} + +void +kfree(void *ptr, size_t n) { + assert(n > 0 && n < 1024*0124); + assert(ptr != NULL); + struct Page *base=NULL; + int num_pages=(n+PGSIZE-1)/PGSIZE; + base = kva2page(ptr); + free_pages(base, num_pages); +} diff --git a/code/lab3/kern/mm/pmm.h b/code/lab3/kern/mm/pmm.h new file mode 100644 index 0000000..003084f --- /dev/null +++ b/code/lab3/kern/mm/pmm.h @@ -0,0 +1,144 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); +struct Page *pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +extern void * kmalloc(size_t n); +extern void kfree(void *ptr, size_t n); +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab3/kern/mm/swap.c b/code/lab3/kern/mm/swap.c new file mode 100644 index 0000000..3004255 --- /dev/null +++ b/code/lab3/kern/mm/swap.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1 +#define CHECK_VALID_VIR_PAGE_NUM 5 +#define BEING_CHECK_VALID_VADDR 0X1000 +#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000 +// the max number of valid physical page for check +#define CHECK_VALID_PHY_PAGE_NUM 4 +// the max access seq number +#define MAX_SEQ_NO 10 + +static struct swap_manager *sm; +size_t max_swap_offset; + +volatile int swap_init_ok = 0; + +unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM]; + +unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO]; + +static void check_swap(void); + +int +swap_init(void) +{ + swapfs_init(); + + if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) + { + panic("bad max_swap_offset %08x.\n", max_swap_offset); + } + + + sm = &swap_manager_fifo; + int r = sm->init(); + + if (r == 0) + { + swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + check_swap(); + } + + return r; +} + +int +swap_init_mm(struct mm_struct *mm) +{ + return sm->init_mm(mm); +} + +int +swap_tick_event(struct mm_struct *mm) +{ + return sm->tick_event(mm); +} + +int +swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + return sm->map_swappable(mm, addr, page, swap_in); +} + +int +swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return sm->set_unswappable(mm, addr); +} + +volatile unsigned int swap_out_num=0; + +int +swap_out(struct mm_struct *mm, int n, int in_tick) +{ + int i; + for (i = 0; i != n; ++ i) + { + uintptr_t v; + //struct Page **ptr_page=NULL; + struct Page *page; + // cprintf("i %d, SWAP: call swap_out_victim\n",i); + int r = sm->swap_out_victim(mm, &page, in_tick); + if (r != 0) { + cprintf("i %d, swap_out: call swap_out_victim failed\n",i); + break; + } + assert(!PageReserved(page)); + + //cprintf("SWAP: choose victim page 0x%08x\n", page); + + v=page->pra_vaddr; + pte_t *ptep = get_pte(mm->pgdir, v, 0); + assert((*ptep & PTE_P) != 0); + + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { + cprintf("SWAP: failed to save\n"); + sm->map_swappable(mm, v, page, 0); + continue; + } + else { + cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; + free_page(page); + } + + tlb_invalidate(mm->pgdir, v); + } + return i; +} + +int +swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) +{ + struct Page *result = alloc_page(); + assert(result!=NULL); + + pte_t *ptep = get_pte(mm->pgdir, addr, 0); + // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); + + int r; + if ((r = swapfs_read((*ptep), result)) != 0) + { + assert(r!=0); + } + cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr); + *ptr_result=result; + return 0; +} + + + +static inline void +check_content_set(void) +{ + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x1010 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x2010 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x3010 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + *(unsigned char *)0x4010 = 0x0d; + assert(pgfault_num==4); +} + +static inline int +check_content_access(void) +{ + int ret = sm->check_swap(); + return ret; +} + +struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM]; +pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM]; +unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM]; + +extern free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +check_swap(void) +{ + //backup mem env + int ret, count = 0, total = 0, i; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + + //now we set the phy pages env + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + extern struct mm_struct *check_mm_struct; + assert(check_mm_struct == NULL); + + check_mm_struct = mm; + + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + //setup the temp Page Table vaddr 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + pte_t *temp_ptep=NULL; + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); + assert(temp_ptep!= NULL); + cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + + for (i=0;iphy_page environment for page relpacement algorithm + + + pgfault_num=0; + + check_content_set(); + assert( nr_free == 0); + for(i = 0; iproperty; + } + + assert(count == 0); + + cprintf("check_swap() succeeded!\n"); +} diff --git a/code/lab3/kern/mm/swap.h b/code/lab3/kern/mm/swap.h new file mode 100644 index 0000000..5d4aea8 --- /dev/null +++ b/code/lab3/kern/mm/swap.h @@ -0,0 +1,65 @@ +#ifndef __KERN_MM_SWAP_H__ +#define __KERN_MM_SWAP_H__ + +#include +#include +#include +#include + +/* * + * swap_entry_t + * -------------------------------------------- + * | offset | reserved | 0 | + * -------------------------------------------- + * 24 bits 7 bits 1 bit + * */ + +#define MAX_SWAP_OFFSET_LIMIT (1 << 24) + +extern size_t max_swap_offset; + +/* * + * swap_offset - takes a swap_entry (saved in pte), and returns + * the corresponding offset in swap mem_map. + * */ +#define swap_offset(entry) ({ \ + size_t __offset = (entry >> 8); \ + if (!(__offset > 0 && __offset < max_swap_offset)) { \ + panic("invalid swap_entry_t = %08x.\n", entry); \ + } \ + __offset; \ + }) + +struct swap_manager +{ + const char *name; + /* Global initialization for the swap manager */ + int (*init) (void); + /* Initialize the priv data inside mm_struct */ + int (*init_mm) (struct mm_struct *mm); + /* Called when tick interrupt occured */ + int (*tick_event) (struct mm_struct *mm); + /* Called when map a swappable page into the mm_struct */ + int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); + /* When a page is marked as shared, this routine is called to + * delete the addr entry from the swap manager */ + int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); + /* Try to swap out a page, return then victim */ + int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); + /* check the page relpacement algorithm */ + int (*check_swap)(void); +}; + +extern volatile int swap_init_ok; +int swap_init(void); +int swap_init_mm(struct mm_struct *mm); +int swap_tick_event(struct mm_struct *mm); +int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); +int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr); +int swap_out(struct mm_struct *mm, int n, int in_tick); +int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result); + +//#define MEMBER_OFFSET(m,t) ((int)(&((t *)0)->m)) +//#define FROM_MEMBER(m,t,a) ((t *)((char *)(a) - MEMBER_OFFSET(m,t))) + +#endif diff --git a/code/lab3/kern/mm/swap_fifo.c b/code/lab3/kern/mm/swap_fifo.c new file mode 100644 index 0000000..4cb00c1 --- /dev/null +++ b/code/lab3/kern/mm/swap_fifo.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out + * page replacement algorithm is a low-overhead algorithm that requires little book-keeping on + * the part of the operating system. The idea is obvious from the name - the operating system + * keeps track of all the pages in memory in a queue, with the most recent arrival at the back, + * and the earliest arrival in front. When a page needs to be replaced, the page at the front + * of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs + * poorly in practical application. Thus, it is rarely used in its unmodified form. This + * algorithm experiences Belady's anomaly. + * + * Details of FIFO PRA + * (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can + * link these pages into pra_list_head according the time order. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list + * implementation. You should know howto USE: list_init, list_add(list_add_after), + * list_add_before, list_del, list_next, list_prev. Another tricky method is to transform + * a general list struct to a special struct (such as struct page). You can find some MACRO: + * le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc. + */ + +list_entry_t pra_list_head; +/* + * (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head. + * Now, From the memory control struct mm_struct, we can access FIFO PRA + */ +static int +_fifo_init_mm(struct mm_struct *mm) +{ + list_init(&pra_list_head); + mm->sm_priv = &pra_list_head; + //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + return 0; +} +/* + * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue + */ +static int +_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + list_entry_t *entry=&(page->pra_page_link); + + assert(entry != NULL && head != NULL); + //record the page access situlation + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + return 0; +} +/* + * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, + * then set the addr of addr of this page to ptr_page. + */ +static int +_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + assert(head != NULL); + assert(in_tick==0); + /* Select the victim */ + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1) unlink the earliest arrival page in front of pra_list_head qeueue + //(2) set the addr of addr of this page to ptr_page + return 0; +} + +static int +_fifo_check_swap(void) { + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==4); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==4); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==4); + cprintf("write Virt Page e in fifo_check_swap\n"); + *(unsigned char *)0x5000 = 0x0e; + assert(pgfault_num==5); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==5); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==6); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==7); + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==8); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==9); + return 0; +} + + +static int +_fifo_init(void) +{ + return 0; +} + +static int +_fifo_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return 0; +} + +static int +_fifo_tick_event(struct mm_struct *mm) +{ return 0; } + + +struct swap_manager swap_manager_fifo = +{ + .name = "fifo swap manager", + .init = &_fifo_init, + .init_mm = &_fifo_init_mm, + .tick_event = &_fifo_tick_event, + .map_swappable = &_fifo_map_swappable, + .set_unswappable = &_fifo_set_unswappable, + .swap_out_victim = &_fifo_swap_out_victim, + .check_swap = &_fifo_check_swap, +}; diff --git a/code/lab3/kern/mm/swap_fifo.h b/code/lab3/kern/mm/swap_fifo.h new file mode 100644 index 0000000..1d74269 --- /dev/null +++ b/code/lab3/kern/mm/swap_fifo.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_SWAP_FIFO_H__ +#define __KERN_MM_SWAP_FIFO_H__ + +#include +extern struct swap_manager swap_manager_fifo; + +#endif diff --git a/code/lab3/kern/mm/vmm.c b/code/lab3/kern/mm/vmm.c new file mode 100644 index 0000000..d374aee --- /dev/null +++ b/code/lab3/kern/mm/vmm.c @@ -0,0 +1,389 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + vmm design include two parts: mm_struct (mm) & vma_struct (vma) + mm is the memory manager for the set of continuous virtual memory + area which have the same PDT. vma is a continuous virtual memory area. + There a linear link list for vma & a redblack link list for vma in mm. +--------------- + mm related functions: + golbal functions + struct mm_struct * mm_create(void) + void mm_destroy(struct mm_struct *mm) + int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) +-------------- + vma related functions: + global functions + struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) + void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) + struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) + local functions + inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) +--------------- + check correctness functions + void check_vmm(void); + void check_vma_struct(void); + void check_pgfault(void); +*/ + +static void check_vmm(void); +static void check_vma_struct(void); +static void check_pgfault(void); + +// mm_create - alloc a mm_struct & initialize it. +struct mm_struct * +mm_create(void) { + struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); + + if (mm != NULL) { + list_init(&(mm->mmap_list)); + mm->mmap_cache = NULL; + mm->pgdir = NULL; + mm->map_count = 0; + + if (swap_init_ok) swap_init_mm(mm); + else mm->sm_priv = NULL; + } + return mm; +} + +// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +struct vma_struct * +vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); + + if (vma != NULL) { + vma->vm_start = vm_start; + vma->vm_end = vm_end; + vma->vm_flags = vm_flags; + } + return vma; +} + + +// find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +struct vma_struct * +find_vma(struct mm_struct *mm, uintptr_t addr) { + struct vma_struct *vma = NULL; + if (mm != NULL) { + vma = mm->mmap_cache; + if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { + bool found = 0; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + vma = le2vma(le, list_link); + if (addr < vma->vm_end) { + found = 1; + break; + } + } + if (!found) { + vma = NULL; + } + } + if (vma != NULL) { + mm->mmap_cache = vma; + } + } + return vma; +} + + +// check_vma_overlap - check if vma1 overlaps vma2 ? +static inline void +check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { + assert(prev->vm_start < prev->vm_end); + assert(prev->vm_end <= next->vm_start); + assert(next->vm_start < next->vm_end); +} + + +// insert_vma_struct -insert vma in mm's list link +void +insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + assert(vma->vm_start < vma->vm_end); + list_entry_t *list = &(mm->mmap_list); + list_entry_t *le_prev = list, *le_next; + + list_entry_t *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *mmap_prev = le2vma(le, list_link); + if (mmap_prev->vm_start > vma->vm_start) { + break; + } + le_prev = le; + } + + le_next = list_next(le_prev); + + /* check overlap */ + if (le_prev != list) { + check_vma_overlap(le2vma(le_prev, list_link), vma); + } + if (le_next != list) { + check_vma_overlap(vma, le2vma(le_next, list_link)); + } + + vma->vm_mm = mm; + list_add_after(le_prev, &(vma->list_link)); + + mm->map_count ++; +} + +// mm_destroy - free mm and mm internal fields +void +mm_destroy(struct mm_struct *mm) { + + list_entry_t *list = &(mm->mmap_list), *le; + while ((le = list_next(list)) != list) { + list_del(le); + kfree(le2vma(le, list_link),sizeof(struct vma_struct)); //kfree vma + } + kfree(mm, sizeof(struct mm_struct)); //kfree mm + mm=NULL; +} + +// vmm_init - initialize virtual memory management +// - now just call check_vmm to check correctness of vmm +void +vmm_init(void) { + check_vmm(); +} + +// check_vmm - check correctness of vmm +static void +check_vmm(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_vma_struct(); + check_pgfault(); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vmm() succeeded.\n"); +} + +static void +check_vma_struct(void) { + size_t nr_free_pages_store = nr_free_pages(); + + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + int step1 = 10, step2 = step1 * 10; + + int i; + for (i = step1; i >= 0; i --) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + for (i = step1 + 1; i <= step2; i ++) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + list_entry_t *le = list_next(&(mm->mmap_list)); + + for (i = 0; i <= step2; i ++) { + assert(le != &(mm->mmap_list)); + struct vma_struct *mmap = le2vma(le, list_link); + assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); + le = list_next(le); + } + + for (i = 0; i < 5 * step2 + 2; i ++) { + struct vma_struct *vma = find_vma(mm, i); + assert(vma != NULL); + int j = i / 5; + if (i >= 5 * j + 2) { + j ++; + } + assert(vma->vm_start == j * 5 && vma->vm_end == j * 5 + 2); + } + + mm_destroy(mm); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vma_struct() succeeded!\n"); +} + +struct mm_struct *check_mm_struct; + +// check_pgfault - check correctness of pgfault handler +static void +check_pgfault(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_mm_struct = mm_create(); + assert(check_mm_struct != NULL); + + struct mm_struct *mm = check_mm_struct; + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + uintptr_t addr = 0x100; + assert(find_vma(mm, addr) == vma); + + int i, sum = 0; + for (i = 0; i < 100; i ++) { + *(char *)(addr + i) = i; + sum += i; + } + for (i = 0; i < 100; i ++) { + sum -= *(char *)(addr + i); + } + assert(sum == 0); + + page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + free_page(pa2page(pgdir[0])); + pgdir[0] = 0; + + mm->pgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_pgfault() succeeded!\n"); +} +//page fault number +volatile unsigned int pgfault_num=0; + +/* do_pgfault - interrupt handler to process the page fault execption + * @mm : the control struct for a set of vma using the same PDT + * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware + * @addr : the addr which causes a memory access exception, (the contents of the CR2 register) + * + * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault + * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing + * the exception and recovering from it. + * (1) The contents of the CR2 register. The processor loads the CR2 register with the + * 32-bit linear address that generated the exception. The do_pgfault fun can + * use this address to locate the corresponding page directory and page-table + * entries. + * (2) An error code on the kernel stack. The error code for a page fault has a format different from + * that for other exceptions. The error code tells the exception handler three things: + * -- The P flag (bit 0) indicates whether the exception was due to a not-present page (0) + * or to either an access rights violation or the use of a reserved bit (1). + * -- The W/R flag (bit 1) indicates whether the memory access that caused the exception + * was a read (0) or write (1). + * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) + * or supervisor mode (0) at the time of the exception. + */ +int +do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { + int ret = -E_INVAL; + //try to find a vma which include addr + struct vma_struct *vma = find_vma(mm, addr); + + pgfault_num++; + //If the addr is in the range of a mm's vma? + if (vma == NULL || vma->vm_start > addr) { + cprintf("not valid addr %x, and can not find it in vma\n", addr); + goto failed; + } + //check the error_code + switch (error_code & 3) { + default: + /* error code flag : default is 3 ( W/R=1, P=1): write, present */ + case 2: /* error code flag : (W/R=1, P=0): write, not present */ + if (!(vma->vm_flags & VM_WRITE)) { + cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); + goto failed; + } + break; + case 1: /* error code flag : (W/R=0, P=1): read, present */ + cprintf("do_pgfault failed: error code flag = read AND present\n"); + goto failed; + case 0: /* error code flag : (W/R=0, P=0): read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { + cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); + goto failed; + } + } + /* IF (write an existed addr ) OR + * (write an non_existed addr && addr is writable) OR + * (read an non_existed addr && addr is readable) + * THEN + * continue process + */ + uint32_t perm = PTE_U; + if (vma->vm_flags & VM_WRITE) { + perm |= PTE_W; + } + addr = ROUNDDOWN(addr, PGSIZE); + + ret = -E_NO_MEM; + + pte_t *ptep=NULL; + /*LAB3 EXERCISE 1: YOUR CODE + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * get_pte : get an pte and return the kernel virtual address of this pte for la + * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') + * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup + * an addr map pa<--->la with linear address la and the PDT pgdir + * DEFINES: + * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + * VARIABLES: + * mm->pgdir : the PDT of these vma + * + */ +#if 0 + /*LAB3 EXERCISE 1: YOUR CODE*/ + ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + if (*ptep == 0) { + //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + + } + else { + /*LAB3 EXERCISE 2: YOUR CODE + * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, + * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, + * find the addr of disk page, read the content of disk page into this memroy page + * page_insert : build the map of phy addr of an Page with the linear addr la + * swap_map_swappable : set the page swappable + */ + if(swap_init_ok) { + struct Page *page=NULL; + //(1)According to the mm AND addr, try to load the content of right disk page + // into the memory which page managed. + //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr + //(3) make the page swappable. + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed; + } + } +#endif + ret = 0; +failed: + return ret; +} + diff --git a/code/lab3/kern/mm/vmm.h b/code/lab3/kern/mm/vmm.h new file mode 100644 index 0000000..c48634b --- /dev/null +++ b/code/lab3/kern/mm/vmm.h @@ -0,0 +1,51 @@ +#ifndef __KERN_MM_VMM_H__ +#define __KERN_MM_VMM_H__ + +#include +#include +#include +#include + +//pre define +struct mm_struct; + +// the virtual continuous memory area(vma) +struct vma_struct { + struct mm_struct *vm_mm; // the set of vma using the same PDT + uintptr_t vm_start; // start addr of vma + uintptr_t vm_end; // end addr of vma + uint32_t vm_flags; // flags of vma + list_entry_t list_link; // linear list link which sorted by start addr of vma +}; + +#define le2vma(le, member) \ + to_struct((le), struct vma_struct, member) + +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 + +// the control struct for a set of vma using the same PDT +struct mm_struct { + list_entry_t mmap_list; // linear list link which sorted by start addr of vma + struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose + pde_t *pgdir; // the PDT of these vma + int map_count; // the count of these vma + void *sm_priv; // the private data for swap manager +}; + +struct vma_struct *find_vma(struct mm_struct *mm, uintptr_t addr); +struct vma_struct *vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags); +void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma); + +struct mm_struct *mm_create(void); +void mm_destroy(struct mm_struct *mm); + +void vmm_init(void); + +int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr); + +extern volatile unsigned int pgfault_num; +extern struct mm_struct *check_mm_struct; +#endif /* !__KERN_MM_VMM_H__ */ + diff --git a/code/lab3/kern/sync/sync.h b/code/lab3/kern/sync/sync.h new file mode 100644 index 0000000..9653377 --- /dev/null +++ b/code/lab3/kern/sync/sync.h @@ -0,0 +1,28 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab3/kern/trap/trap.c b/code/lab3/kern/trap/trap.c new file mode 100644 index 0000000..90f266e --- /dev/null +++ b/code/lab3/kern/trap/trap.c @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static inline void +print_pgfault(struct trapframe *tf) { + /* error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * bit 2 == 0 means kernel, 1 means user + * */ + cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), + (tf->tf_err & 4) ? 'U' : 'K', + (tf->tf_err & 2) ? 'W' : 'R', + (tf->tf_err & 1) ? "protection fault" : "no page found"); +} + +static int +pgfault_handler(struct trapframe *tf) { + extern struct mm_struct *check_mm_struct; + print_pgfault(tf); + if (check_mm_struct != NULL) { + return do_pgfault(check_mm_struct, tf->tf_err, rcr2()); + } + panic("unhandled page fault.\n"); +} + +static volatile int in_swap_tick_event = 0; +extern struct mm_struct *check_mm_struct; + +static void +trap_dispatch(struct trapframe *tf) { + char c; + + int ret; + + switch (tf->tf_trapno) { + case T_PGFLT: //page fault + if ((ret = pgfault_handler(tf)) != 0) { + print_trapframe(tf); + panic("handle pgfault failed. %e\n", ret); + } + break; + case IRQ_OFFSET + IRQ_TIMER: +#if 0 + LAB3 : If some page replacement algorithm(such as CLOCK PRA) need tick to change the priority of pages, + then you can add code here. +#endif + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + // in kernel, it must be a mistake + if ((tf->tf_cs & 3) == 0) { + print_trapframe(tf); + panic("unexpected trap in kernel.\n"); + } + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + trap_dispatch(tf); +} + diff --git a/code/lab3/kern/trap/trap.h b/code/lab3/kern/trap/trap.h new file mode 100644 index 0000000..74d973d --- /dev/null +++ b/code/lab3/kern/trap/trap.h @@ -0,0 +1,91 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +#define T_SYSCALL 0x80 // SYSCALL, ONLY FOR THIS PROJ + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab3/kern/trap/trapentry.S b/code/lab3/kern/trap/trapentry.S new file mode 100644 index 0000000..55e29b7 --- /dev/null +++ b/code/lab3/kern/trap/trapentry.S @@ -0,0 +1,44 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + diff --git a/code/lab3/kern/trap/vectors.S b/code/lab3/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab3/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab3/libs/atomic.h b/code/lab3/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab3/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab3/libs/defs.h b/code/lab3/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab3/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab3/libs/elf.h b/code/lab3/libs/elf.h new file mode 100644 index 0000000..bdfee3d --- /dev/null +++ b/code/lab3/libs/elf.h @@ -0,0 +1,40 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab3/libs/error.h b/code/lab3/libs/error.h new file mode 100644 index 0000000..b43fbd6 --- /dev/null +++ b/code/lab3/libs/error.h @@ -0,0 +1,16 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault + +/* the maximum allowed */ +#define MAXERROR 6 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab3/libs/list.h b/code/lab3/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab3/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab3/libs/printfmt.c b/code/lab3/libs/printfmt.c new file mode 100644 index 0000000..93a1c47 --- /dev/null +++ b/code/lab3/libs/printfmt.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab3/libs/rand.c b/code/lab3/libs/rand.c new file mode 100644 index 0000000..2a2c4e7 --- /dev/null +++ b/code/lab3/libs/rand.c @@ -0,0 +1,26 @@ +#include +#include + +static unsigned long long next = 1; + +/* * + * rand - returns a pseudo-random integer + * + * The rand() function return a value in the range [0, RAND_MAX]. + * */ +int +rand(void) { + next = (next * 0x5DEECE66DLL + 0xBLL) & ((1LL << 48) - 1); + unsigned long long result = (next >> 12); + return (int)do_div(result, RAND_MAX + 1); +} + +/* * + * srand - seed the random number generator with the given number + * @seed: the required seed number + * */ +void +srand(unsigned int seed) { + next = seed; +} + diff --git a/code/lab3/libs/stdarg.h b/code/lab3/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab3/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab3/libs/stdio.h b/code/lab3/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab3/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab3/libs/stdlib.h b/code/lab3/libs/stdlib.h new file mode 100644 index 0000000..297e778 --- /dev/null +++ b/code/lab3/libs/stdlib.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDLIB_H__ +#define __LIBS_STDLIB_H__ + +/* the largest number rand will return */ +#define RAND_MAX 2147483647UL + +/* libs/rand.c */ +int rand(void); +void srand(unsigned int seed); + +#endif /* !__LIBS_RAND_H__ */ + diff --git a/code/lab3/libs/string.c b/code/lab3/libs/string.c new file mode 100644 index 0000000..bcf1b1c --- /dev/null +++ b/code/lab3/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab3/libs/string.h b/code/lab3/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab3/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab3/libs/x86.h b/code/lab3/libs/x86.h new file mode 100644 index 0000000..b29f671 --- /dev/null +++ b/code/lab3/libs/x86.h @@ -0,0 +1,302 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab3/tools/boot.ld b/code/lab3/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab3/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab3/tools/function.mk b/code/lab3/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab3/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab3/tools/gdbinit b/code/lab3/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab3/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab3/tools/grade.sh b/code/lab3/tools/grade.sh new file mode 100644 index 0000000..a6f2425 --- /dev/null +++ b/code/lab3/tools/grade.sh @@ -0,0 +1,364 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-Ddef...] [-check ] checkargs ... + tag= + check=check_regexps + while true; do + select= + case $1 in + -tag) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## swap image +swapimg=$(make_print swapimg) + +## set default qemu-options +qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback" + +## set break-function, default is readline +brkfun=readline + +## check now!! + +quick_run 'Check SWAP' + +pts=5 +quick_check 'check pmm' \ + 'memory management: default_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' + +pts=5 +quick_check 'check page table' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' + +pts=10 +quick_check 'check vmm' \ + 'check_vma_struct() succeeded!' \ + 'page fault at 0x00000100: K/W [no page found].' \ + 'check_pgfault() succeeded!' \ + 'check_vmm() succeeded.' + +pts=20 +quick_check 'check swap page fault' \ + 'page fault at 0x00001000: K/W [no page found].' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'write Virt Page e in fifo_check_swap' \ + 'page fault at 0x00005000: K/W [no page found].' \ + 'page fault at 0x00001000: K/W [no page found]' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'check_swap() succeeded!' + +pts=5 +quick_check 'check ticks' \ + '++ setup timer interrupts' \ + '100 ticks' \ + 'End of Test.' + +## print final-score +show_final + diff --git a/code/lab3/tools/kernel.ld b/code/lab3/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab3/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab3/tools/sign.c b/code/lab3/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab3/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab3/tools/vector.c b/code/lab3/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab3/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab4/Makefile b/code/lab4/Makefile new file mode 100644 index 0000000..bc3349a --- /dev/null +++ b/code/lab4/Makefile @@ -0,0 +1,275 @@ +PROJ := 8 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +GDB := $(GCCPREFIX)gdb + +CC ?= $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ \ + kern/fs/ \ + kern/process \ + kern/schedule + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync \ + kern/fs \ + kern/process \ + kern/schedule + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# ------------------------------------------------------------------- + +# create swap.img +SWAPIMG := $(call totarget,swap.img) + +$(SWAPIMG): + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + +$(call create_target,swap.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback + +.PHONY: qemu qemu-nox debug debug-nox +qemu: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +TERMINAL := gnome-terminal + +debug: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := proj$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/trap/trap.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab4/boot/asm.h b/code/lab4/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab4/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab4/boot/bootasm.S b/code/lab4/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab4/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab4/boot/bootmain.c b/code/lab4/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab4/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab4/kern/debug/assert.h b/code/lab4/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab4/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab4/kern/debug/kdebug.c b/code/lab4/kern/debug/kdebug.c new file mode 100644 index 0000000..fbf7346 --- /dev/null +++ b/code/lab4/kern/debug/kdebug.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab4/kern/debug/kdebug.h b/code/lab4/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab4/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab4/kern/debug/monitor.c b/code/lab4/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab4/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab4/kern/debug/monitor.h b/code/lab4/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab4/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab4/kern/debug/panic.c b/code/lab4/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab4/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab4/kern/debug/stab.h b/code/lab4/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab4/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab4/kern/driver/clock.c b/code/lab4/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab4/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab4/kern/driver/clock.h b/code/lab4/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab4/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab4/kern/driver/console.c b/code/lab4/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab4/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab4/kern/driver/console.h b/code/lab4/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab4/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab4/kern/driver/ide.c b/code/lab4/kern/driver/ide.c new file mode 100644 index 0000000..271cfa8 --- /dev/null +++ b/code/lab4/kern/driver/ide.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA_DATA 0x00 +#define ISA_ERROR 0x01 +#define ISA_PRECOMP 0x01 +#define ISA_CTRL 0x02 +#define ISA_SECCNT 0x02 +#define ISA_SECTOR 0x03 +#define ISA_CYL_LO 0x04 +#define ISA_CYL_HI 0x05 +#define ISA_SDH 0x06 +#define ISA_COMMAND 0x07 +#define ISA_STATUS 0x07 + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_DRQ 0x08 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_IDENTIFY 0xEC + +#define IDE_IDENT_SECTORS 20 +#define IDE_IDENT_MODEL 54 +#define IDE_IDENT_CAPABILITIES 98 +#define IDE_IDENT_CMDSETS 164 +#define IDE_IDENT_MAX_LBA 120 +#define IDE_IDENT_MAX_LBA_EXT 200 + +#define IO_BASE0 0x1F0 +#define IO_BASE1 0x170 +#define IO_CTRL0 0x3F4 +#define IO_CTRL1 0x374 + +#define MAX_IDE 4 +#define MAX_NSECS 128 +#define MAX_DISK_NSECS 0x10000000U +#define VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) + +static const struct { + unsigned short base; // I/O Base + unsigned short ctrl; // Control Base +} channels[2] = { + {IO_BASE0, IO_CTRL0}, + {IO_BASE1, IO_CTRL1}, +}; + +#define IO_BASE(ideno) (channels[(ideno) >> 1].base) +#define IO_CTRL(ideno) (channels[(ideno) >> 1].ctrl) + +static struct ide_device { + unsigned char valid; // 0 or 1 (If Device Really Exists) + unsigned int sets; // Commend Sets Supported + unsigned int size; // Size in Sectors + unsigned char model[41]; // Model in String +} ide_devices[MAX_IDE]; + +static int +ide_wait_ready(unsigned short iobase, bool check_error) { + int r; + while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY) + /* nothing */; + if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) { + return -1; + } + return 0; +} + +void +ide_init(void) { + static_assert((SECTSIZE % 4) == 0); + unsigned short ideno, iobase; + for (ideno = 0; ideno < MAX_IDE; ideno ++) { + /* assume that no device here */ + ide_devices[ideno].valid = 0; + + iobase = IO_BASE(ideno); + + /* wait device ready */ + ide_wait_ready(iobase, 0); + + /* step1: select drive */ + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4)); + ide_wait_ready(iobase, 0); + + /* step2: send ATA identify command */ + outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); + ide_wait_ready(iobase, 0); + + /* step3: polling */ + if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) { + continue ; + } + + /* device is ok */ + ide_devices[ideno].valid = 1; + + /* read identification space of the device */ + unsigned int buffer[128]; + insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); + + unsigned char *ident = (unsigned char *)buffer; + unsigned int sectors; + unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); + /* device use 48-bits or 28-bits addressing */ + if (cmdsets & (1 << 26)) { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); + } + else { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); + } + ide_devices[ideno].sets = cmdsets; + ide_devices[ideno].size = sectors; + + /* check if supports LBA */ + assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); + + unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; + unsigned int i, length = 40; + for (i = 0; i < length; i += 2) { + model[i] = data[i + 1], model[i + 1] = data[i]; + } + do { + model[i] = '\0'; + } while (i -- > 0 && model[i] == ' '); + + cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model); + } + + // enable ide interrupt + pic_enable(IRQ_IDE1); + pic_enable(IRQ_IDE2); +} + +bool +ide_device_valid(unsigned short ideno) { + return VALID_IDE(ideno); +} + +size_t +ide_device_size(unsigned short ideno) { + if (ide_device_valid(ideno)) { + return ide_devices[ideno].size; + } + return 0; +} + +int +ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_READ); + + int ret = 0; + for (; nsecs > 0; nsecs --, dst += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + insl(iobase, dst, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + +int +ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); + + int ret = 0; + for (; nsecs > 0; nsecs --, src += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + outsl(iobase, src, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + diff --git a/code/lab4/kern/driver/ide.h b/code/lab4/kern/driver/ide.h new file mode 100644 index 0000000..3e3fd21 --- /dev/null +++ b/code/lab4/kern/driver/ide.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_IDE_H__ +#define __KERN_DRIVER_IDE_H__ + +#include + +void ide_init(void); +bool ide_device_valid(unsigned short ideno); +size_t ide_device_size(unsigned short ideno); + +int ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs); +int ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs); + +#endif /* !__KERN_DRIVER_IDE_H__ */ + diff --git a/code/lab4/kern/driver/intr.c b/code/lab4/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab4/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab4/kern/driver/intr.h b/code/lab4/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab4/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab4/kern/driver/kbdreg.h b/code/lab4/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab4/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab4/kern/driver/picirq.c b/code/lab4/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab4/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab4/kern/driver/picirq.h b/code/lab4/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab4/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab4/kern/fs/fs.h b/code/lab4/kern/fs/fs.h new file mode 100644 index 0000000..92c05e7 --- /dev/null +++ b/code/lab4/kern/fs/fs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_FS_H__ +#define __KERN_FS_FS_H__ + +#include + +#define SECTSIZE 512 +#define PAGE_NSECT (PGSIZE / SECTSIZE) + +#define SWAP_DEV_NO 1 + +#endif /* !__KERN_FS_FS_H__ */ + diff --git a/code/lab4/kern/fs/swapfs.c b/code/lab4/kern/fs/swapfs.c new file mode 100644 index 0000000..d9f6090 --- /dev/null +++ b/code/lab4/kern/fs/swapfs.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include + +void +swapfs_init(void) { + static_assert((PGSIZE % SECTSIZE) == 0); + if (!ide_device_valid(SWAP_DEV_NO)) { + panic("swap fs isn't available.\n"); + } + max_swap_offset = ide_device_size(SWAP_DEV_NO) / (PGSIZE / SECTSIZE); +} + +int +swapfs_read(swap_entry_t entry, struct Page *page) { + return ide_read_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + +int +swapfs_write(swap_entry_t entry, struct Page *page) { + return ide_write_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + diff --git a/code/lab4/kern/fs/swapfs.h b/code/lab4/kern/fs/swapfs.h new file mode 100644 index 0000000..d433926 --- /dev/null +++ b/code/lab4/kern/fs/swapfs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_SWAPFS_H__ +#define __KERN_FS_SWAPFS_H__ + +#include +#include + +void swapfs_init(void); +int swapfs_read(swap_entry_t entry, struct Page *page); +int swapfs_write(swap_entry_t entry, struct Page *page); + +#endif /* !__KERN_FS_SWAPFS_H__ */ + diff --git a/code/lab4/kern/init/entry.S b/code/lab4/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab4/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab4/kern/init/init.c b/code/lab4/kern/init/init.c new file mode 100644 index 0000000..5546347 --- /dev/null +++ b/code/lab4/kern/init/init.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + vmm_init(); // init virtual memory management + proc_init(); // init process table + + ide_init(); // init ide devices + swap_init(); // init swap + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + cpu_idle(); // run idle process +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab4/kern/libs/rb_tree.c b/code/lab4/kern/libs/rb_tree.c new file mode 100644 index 0000000..0a5fcc8 --- /dev/null +++ b/code/lab4/kern/libs/rb_tree.c @@ -0,0 +1,528 @@ +#include +#include +#include +#include +#include +#include + +/* rb_node_create - create a new rb_node */ +static inline rb_node * +rb_node_create(void) { + return kmalloc(sizeof(rb_node)); +} + +/* rb_tree_empty - tests if tree is empty */ +static inline bool +rb_tree_empty(rb_tree *tree) { + rb_node *nil = tree->nil, *root = tree->root; + return root->left == nil; +} + +/* * + * rb_tree_create - creates a new red-black tree, the 'compare' function + * is required and returns 'NULL' if failed. + * + * Note that, root->left should always point to the node that is the root + * of the tree. And nil points to a 'NULL' node which should always be + * black and may have arbitrary children and parent node. + * */ +rb_tree * +rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)) { + assert(compare != NULL); + + rb_tree *tree; + rb_node *nil, *root; + + if ((tree = kmalloc(sizeof(rb_tree))) == NULL) { + goto bad_tree; + } + + tree->compare = compare; + + if ((nil = rb_node_create()) == NULL) { + goto bad_node_cleanup_tree; + } + + nil->parent = nil->left = nil->right = nil; + nil->red = 0; + tree->nil = nil; + + if ((root = rb_node_create()) == NULL) { + goto bad_node_cleanup_nil; + } + + root->parent = root->left = root->right = nil; + root->red = 0; + tree->root = root; + return tree; + +bad_node_cleanup_nil: + kfree(nil); +bad_node_cleanup_tree: + kfree(tree); +bad_tree: + return NULL; +} + +/* * + * FUNC_ROTATE - rotates as described in "Introduction to Algorithm". + * + * For example, FUNC_ROTATE(rb_left_rotate, left, right) can be expaned to a + * left-rotate function, which requires an red-black 'tree' and a node 'x' + * to be rotated on. Basically, this function, named rb_left_rotate, makes the + * parent of 'x' be the left child of 'x', 'x' the parent of its parent before + * rotation and finally fixes other nodes accordingly. + * + * FUNC_ROTATE(xx, left, right) means left-rotate, + * and FUNC_ROTATE(xx, right, left) means right-rotate. + * */ +#define FUNC_ROTATE(func_name, _left, _right) \ +static void \ +func_name(rb_tree *tree, rb_node *x) { \ + rb_node *nil = tree->nil, *y = x->_right; \ + assert(x != tree->root && x != nil && y != nil); \ + x->_right = y->_left; \ + if (y->_left != nil) { \ + y->_left->parent = x; \ + } \ + y->parent = x->parent; \ + if (x == x->parent->_left) { \ + x->parent->_left = y; \ + } \ + else { \ + x->parent->_right = y; \ + } \ + y->_left = x; \ + x->parent = y; \ + assert(!(nil->red)); \ +} + +FUNC_ROTATE(rb_left_rotate, left, right); +FUNC_ROTATE(rb_right_rotate, right, left); + +#undef FUNC_ROTATE + +#define COMPARE(tree, node1, node2) \ + ((tree))->compare((node1), (node2)) + +/* * + * rb_insert_binary - insert @node to red-black @tree as if it were + * a regular binary tree. This function is only intended to be called + * by function rb_insert. + * */ +static inline void +rb_insert_binary(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node, *nil = tree->nil, *root = tree->root; + + z->left = z->right = nil; + y = root, x = y->left; + while (x != nil) { + y = x; + x = (COMPARE(tree, x, node) > 0) ? x->left : x->right; + } + z->parent = y; + if (y == root || COMPARE(tree, y, z) > 0) { + y->left = z; + } + else { + y->right = z; + } +} + +/* rb_insert - insert a node to red-black tree */ +void +rb_insert(rb_tree *tree, rb_node *node) { + rb_insert_binary(tree, node); + node->red = 1; + + rb_node *x = node, *y; + +#define RB_INSERT_SUB(_left, _right) \ + do { \ + y = x->parent->parent->_right; \ + if (y->red) { \ + x->parent->red = 0; \ + y->red = 0; \ + x->parent->parent->red = 1; \ + x = x->parent->parent; \ + } \ + else { \ + if (x == x->parent->_right) { \ + x = x->parent; \ + rb_##_left##_rotate(tree, x); \ + } \ + x->parent->red = 0; \ + x->parent->parent->red = 1; \ + rb_##_right##_rotate(tree, x->parent->parent); \ + } \ + } while (0) + + while (x->parent->red) { + if (x->parent == x->parent->parent->left) { + RB_INSERT_SUB(left, right); + } + else { + RB_INSERT_SUB(right, left); + } + } + tree->root->left->red = 0; + assert(!(tree->nil->red) && !(tree->root->red)); + +#undef RB_INSERT_SUB +} + +/* * + * rb_tree_successor - returns the successor of @node, or nil + * if no successor exists. Make sure that @node must belong to @tree, + * and this function should only be called by rb_node_prev. + * */ +static inline rb_node * +rb_tree_successor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->right) != nil) { + while (y->left != nil) { + y = y->left; + } + return y; + } + else { + y = x->parent; + while (x == y->right) { + x = y, y = y->parent; + } + if (y == tree->root) { + return nil; + } + return y; + } +} + +/* * + * rb_tree_predecessor - returns the predecessor of @node, or nil + * if no predecessor exists, likes rb_tree_successor. + * */ +static inline rb_node * +rb_tree_predecessor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->left) != nil) { + while (y->right != nil) { + y = y->right; + } + return y; + } + else { + y = x->parent; + while (x == y->left) { + if (y == tree->root) { + return nil; + } + x = y, y = y->parent; + } + return y; + } +} + +/* * + * rb_search - returns a node with value 'equal' to @key (according to + * function @compare). If there're multiple nodes with value 'equal' to @key, + * the functions returns the one highest in the tree. + * */ +rb_node * +rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key) { + rb_node *nil = tree->nil, *node = tree->root->left; + int r; + while (node != nil && (r = compare(node, key)) != 0) { + node = (r > 0) ? node->left : node->right; + } + return (node != nil) ? node : NULL; +} + +/* * + * rb_delete_fixup - performs rotations and changes colors to restore + * red-black properties after a node is deleted. + * */ +static void +rb_delete_fixup(rb_tree *tree, rb_node *node) { + rb_node *x = node, *w, *root = tree->root->left; + +#define RB_DELETE_FIXUP_SUB(_left, _right) \ + do { \ + w = x->parent->_right; \ + if (w->red) { \ + w->red = 0; \ + x->parent->red = 1; \ + rb_##_left##_rotate(tree, x->parent); \ + w = x->parent->_right; \ + } \ + if (!w->_left->red && !w->_right->red) { \ + w->red = 1; \ + x = x->parent; \ + } \ + else { \ + if (!w->_right->red) { \ + w->_left->red = 0; \ + w->red = 1; \ + rb_##_right##_rotate(tree, w); \ + w = x->parent->_right; \ + } \ + w->red = x->parent->red; \ + x->parent->red = 0; \ + w->_right->red = 0; \ + rb_##_left##_rotate(tree, x->parent); \ + x = root; \ + } \ + } while (0) + + while (x != root && !x->red) { + if (x == x->parent->left) { + RB_DELETE_FIXUP_SUB(left, right); + } + else { + RB_DELETE_FIXUP_SUB(right, left); + } + } + x->red = 0; + +#undef RB_DELETE_FIXUP_SUB +} + +/* * + * rb_delete - deletes @node from @tree, and calls rb_delete_fixup to + * restore red-black properties. + * */ +void +rb_delete(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node; + rb_node *nil = tree->nil, *root = tree->root; + + y = (z->left == nil || z->right == nil) ? z : rb_tree_successor(tree, z); + x = (y->left != nil) ? y->left : y->right; + + assert(y != root && y != nil); + + x->parent = y->parent; + if (y == y->parent->left) { + y->parent->left = x; + } + else { + y->parent->right = x; + } + + bool need_fixup = !(y->red); + + if (y != z) { + if (z == z->parent->left) { + z->parent->left = y; + } + else { + z->parent->right = y; + } + z->left->parent = z->right->parent = y; + *y = *z; + } + if (need_fixup) { + rb_delete_fixup(tree, x); + } +} + +/* rb_tree_destroy - destroy a tree and free memory */ +void +rb_tree_destroy(rb_tree *tree) { + kfree(tree->root); + kfree(tree->nil); + kfree(tree); +} + +/* * + * rb_node_prev - returns the predecessor node of @node in @tree, + * or 'NULL' if no predecessor exists. + * */ +rb_node * +rb_node_prev(rb_tree *tree, rb_node *node) { + rb_node *prev = rb_tree_predecessor(tree, node); + return (prev != tree->nil) ? prev : NULL; +} + +/* * + * rb_node_next - returns the successor node of @node in @tree, + * or 'NULL' if no successor exists. + * */ +rb_node * +rb_node_next(rb_tree *tree, rb_node *node) { + rb_node *next = rb_tree_successor(tree, node); + return (next != tree->nil) ? next : NULL; +} + +/* rb_node_root - returns the root node of a @tree, or 'NULL' if tree is empty */ +rb_node * +rb_node_root(rb_tree *tree) { + rb_node *node = tree->root->left; + return (node != tree->nil) ? node : NULL; +} + +/* rb_node_left - gets the left child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_left(rb_tree *tree, rb_node *node) { + rb_node *left = node->left; + return (left != tree->nil) ? left : NULL; +} + +/* rb_node_right - gets the right child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_right(rb_tree *tree, rb_node *node) { + rb_node *right = node->right; + return (right != tree->nil) ? right : NULL; +} + +int +check_tree(rb_tree *tree, rb_node *node) { + rb_node *nil = tree->nil; + if (node == nil) { + assert(!node->red); + return 1; + } + if (node->left != nil) { + assert(COMPARE(tree, node, node->left) >= 0); + assert(node->left->parent == node); + } + if (node->right != nil) { + assert(COMPARE(tree, node, node->right) <= 0); + assert(node->right->parent == node); + } + if (node->red) { + assert(!node->left->red && !node->right->red); + } + int hb_left = check_tree(tree, node->left); + int hb_right = check_tree(tree, node->right); + assert(hb_left == hb_right); + int hb = hb_left; + if (!node->red) { + hb ++; + } + return hb; +} + +static void * +check_safe_kmalloc(size_t size) { + void *ret = kmalloc(size); + assert(ret != NULL); + return ret; +} + +struct check_data { + long data; + rb_node rb_link; +}; + +#define rbn2data(node) \ + (to_struct(node, struct check_data, rb_link)) + +static inline int +check_compare1(rb_node *node1, rb_node *node2) { + return rbn2data(node1)->data - rbn2data(node2)->data; +} + +static inline int +check_compare2(rb_node *node, void *key) { + return rbn2data(node)->data - (long)key; +} + +void +check_rb_tree(void) { + rb_tree *tree = rb_tree_create(check_compare1); + assert(tree != NULL); + + rb_node *nil = tree->nil, *root = tree->root; + assert(!nil->red && root->left == nil); + + int total = 1000; + struct check_data **all = check_safe_kmalloc(sizeof(struct check_data *) * total); + + long i; + for (i = 0; i < total; i ++) { + all[i] = check_safe_kmalloc(sizeof(struct check_data)); + all[i]->data = i; + } + + int *mark = check_safe_kmalloc(sizeof(int) * total); + memset(mark, 0, sizeof(int) * total); + + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + int j = (rand() % (total - i)) + i; + struct check_data *z = all[i]; + all[i] = all[j]; + all[j] = z; + } + + memset(mark, 0, sizeof(int) * total); + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_node *node; + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)(all[i]->data)); + assert(node != NULL && node == &(all[i]->rb_link)); + } + + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)i); + assert(node != NULL && rbn2data(node)->data == i); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(!nil->red && root->left == nil); + + long max = 32; + if (max > total) { + max = total; + } + + for (i = 0; i < max; i ++) { + all[i]->data = max; + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + for (i = 0; i < max; i ++) { + node = rb_search(tree, check_compare2, (void *)max); + assert(node != NULL && rbn2data(node)->data == max); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(rb_tree_empty(tree)); + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_tree_destroy(tree); + + for (i = 0; i < total; i ++) { + kfree(all[i]); + } + + kfree(mark); + kfree(all); +} + diff --git a/code/lab4/kern/libs/rb_tree.h b/code/lab4/kern/libs/rb_tree.h new file mode 100644 index 0000000..a2aa9aa --- /dev/null +++ b/code/lab4/kern/libs/rb_tree.h @@ -0,0 +1,32 @@ +#ifndef __KERN_LIBS_RB_TREE_H__ +#define __KERN_LIBS_RB_TREE_H__ + +#include + +typedef struct rb_node { + bool red; // if red = 0, it's a black node + struct rb_node *parent; + struct rb_node *left, *right; +} rb_node; + +typedef struct rb_tree { + // compare function should return -1 if *node1 < *node2, 1 if *node1 > *node2, and 0 otherwise + int (*compare)(rb_node *node1, rb_node *node2); + struct rb_node *nil, *root; +} rb_tree; + +rb_tree *rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)); +void rb_tree_destroy(rb_tree *tree); +void rb_insert(rb_tree *tree, rb_node *node); +void rb_delete(rb_tree *tree, rb_node *node); +rb_node *rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key); +rb_node *rb_node_prev(rb_tree *tree, rb_node *node); +rb_node *rb_node_next(rb_tree *tree, rb_node *node); +rb_node *rb_node_root(rb_tree *tree); +rb_node *rb_node_left(rb_tree *tree, rb_node *node); +rb_node *rb_node_right(rb_tree *tree, rb_node *node); + +void check_rb_tree(void); + +#endif /* !__KERN_LIBS_RBTREE_H__ */ + diff --git a/code/lab4/kern/libs/readline.c b/code/lab4/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab4/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab4/kern/libs/stdio.c b/code/lab4/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab4/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab4/kern/mm/default_pmm.c b/code/lab4/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab4/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab4/kern/mm/default_pmm.h b/code/lab4/kern/mm/default_pmm.h new file mode 100644 index 0000000..07352aa --- /dev/null +++ b/code/lab4/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; + +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab4/kern/mm/kmalloc.c b/code/lab4/kern/mm/kmalloc.c new file mode 100644 index 0000000..8056bff --- /dev/null +++ b/code/lab4/kern/mm/kmalloc.c @@ -0,0 +1,635 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The slab allocator used in ucore is based on an algorithm first introduced by + Jeff Bonwick for the SunOS operating system. The paper can be download from + http://citeseer.ist.psu.edu/bonwick94slab.html + An implementation of the Slab Allocator as described in outline in; + UNIX Internals: The New Frontiers by Uresh Vahalia + Pub: Prentice Hall ISBN 0-13-101908-2 + Within a kernel, a considerable amount of memory is allocated for a finite set + of objects such as file descriptors and other common structures. Jeff found that + the amount of time required to initialize a regular object in the kernel exceeded + the amount of time required to allocate and deallocate it. His conclusion was + that instead of freeing the memory back to a global pool, he would have the memory + remain initialized for its intended purpose. + In our simple slab implementation, the the high-level organization of the slab + structures is simplied. At the highest level is an array slab_cache[SLAB_CACHE_NUM], + and each array element is a slab_cache which has slab chains. Each slab_cache has + two list, one list chains the full allocated slab, and another list chains the notfull + allocated(maybe empty) slab. And each slab has fixed number(2^n) of pages. In each + slab, there are a lot of objects (such as ) with same fixed size(32B ~ 128KB). + + +----------------------------------+ + | slab_cache[0] for 0~32B obj | + +----------------------------------+ + | slab_cache[1] for 33B~64B obj |-->lists for slabs + +----------------------------------+ | + | slab_cache[2] for 65B~128B obj | | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + +----------------------------------+ | + | slab_cache[12]for 64KB~128KB obj | | + +----------------------------------+ | + | + slabs_full/slabs_not +---------------------+ + -<-----------<----------<-+ + | | | + slab1 slab2 slab3... + | + |-------|-------| + pages1 pages2 pages3... + | + | + | + slab_t+n*bufctl_t+obj1-obj2-obj3...objn (the size of obj is small) + | + OR + | + obj1-obj2-obj3...objn WITH slab_t+n*bufctl_t in another slab (the size of obj is BIG) + + The important functions are: + kmem_cache_grow(kmem_cache_t *cachep) + kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) + kmalloc(size_t size): used by outside functions need dynamicly get memory + kfree(void *objp): used by outside functions need dynamicly release memory +*/ + +#define BUFCTL_END 0xFFFFFFFFL // the signature of the last bufctl +#define SLAB_LIMIT 0xFFFFFFFEL // the max value of obj number + +typedef size_t kmem_bufctl_t; //the index of obj in slab + +typedef struct slab_s { + list_entry_t slab_link; // the list entry linked to kmem_cache list + void *s_mem; // the kernel virtual address of the first obj in slab + size_t inuse; // the number of allocated objs + size_t offset; // the first obj's offset value in slab + kmem_bufctl_t free; // the first free obj's index in slab +} slab_t; + +// get the slab address according to the link element (see list.h) +#define le2slab(le, member) \ + to_struct((le), slab_t, member) + +typedef struct kmem_cache_s kmem_cache_t; + + +struct kmem_cache_s { + list_entry_t slabs_full; // list for fully allocated slabs + list_entry_t slabs_notfull; // list for not-fully allocated slabs + + size_t objsize; // the fixed size of obj + size_t num; // number of objs per slab + size_t offset; // this first obj's offset in slab + bool off_slab; // the control part of slab in slab or not. + + /* order of pages per slab (2^n) */ + size_t page_order; + + kmem_cache_t *slab_cachep; +}; + +#define MIN_SIZE_ORDER 5 // 32 +#define MAX_SIZE_ORDER 17 // 128k +#define SLAB_CACHE_NUM (MAX_SIZE_ORDER - MIN_SIZE_ORDER + 1) + +static kmem_cache_t slab_cache[SLAB_CACHE_NUM]; + +static void init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align); +static void check_slab(void); + + +//slab_init - call init_kmem_cache function to reset the slab_cache array +static void +slab_init(void) { + size_t i; + //the align bit for obj in slab. 2^n could be better for performance + size_t align = 16; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + init_kmem_cache(slab_cache + i, 1 << (i + MIN_SIZE_ORDER), align); + } + check_slab(); +} + +inline void +kmalloc_init(void) { + slab_init(); + cprintf("kmalloc_init() succeeded!\n"); +} + +//slab_allocated - summary the total size of allocated objs +static size_t +slab_allocated(void) { + size_t total = 0; + int i; + bool intr_flag; + local_intr_save(intr_flag); + { + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + list_entry_t *list, *le; + list = le = &(cachep->slabs_full); + while ((le = list_next(le)) != list) { + total += cachep->num * cachep->objsize; + } + list = le = &(cachep->slabs_notfull); + while ((le = list_next(le)) != list) { + slab_t *slabp = le2slab(le, slab_link); + total += slabp->inuse * cachep->objsize; + } + } + } + local_intr_restore(intr_flag); + return total; +} + +// slab_mgmt_size - get the size of slab control area (slab_t+num*kmem_bufctl_t) +static size_t +slab_mgmt_size(size_t num, size_t align) { + return ROUNDUP(sizeof(slab_t) + num * sizeof(kmem_bufctl_t), align); +} + +// cacahe_estimate - estimate the number of objs in a slab +static void +cache_estimate(size_t order, size_t objsize, size_t align, bool off_slab, size_t *remainder, size_t *num) { + size_t nr_objs, mgmt_size; + size_t slab_size = (PGSIZE << order); + + if (off_slab) { + mgmt_size = 0; + nr_objs = slab_size / objsize; + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + } + else { + nr_objs = (slab_size - sizeof(slab_t)) / (objsize + sizeof(kmem_bufctl_t)); + while (slab_mgmt_size(nr_objs, align) + nr_objs * objsize > slab_size) { + nr_objs --; + } + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + mgmt_size = slab_mgmt_size(nr_objs, align); + } + *num = nr_objs; + *remainder = slab_size - nr_objs * objsize - mgmt_size; +} + +// calculate_slab_order - estimate the size(4K~4M) of slab +// paramemters: +// cachep: the slab_cache +// objsize: the size of obj +// align: align bit for objs +// off_slab: the control part of slab in slab or not +// left_over: the size of can not be used area in slab +static void +calculate_slab_order(kmem_cache_t *cachep, size_t objsize, size_t align, bool off_slab, size_t *left_over) { + size_t order; + for (order = 0; order <= KMALLOC_MAX_ORDER; order ++) { + size_t num, remainder; + cache_estimate(order, objsize, align, off_slab, &remainder, &num); + if (num != 0) { + if (off_slab) { + size_t off_slab_limit = objsize - sizeof(slab_t); + off_slab_limit /= sizeof(kmem_bufctl_t); + if (num > off_slab_limit) { + panic("off_slab: objsize = %d, num = %d.", objsize, num); + } + } + if (remainder * 8 <= (PGSIZE << order)) { + cachep->num = num; + cachep->page_order = order; + if (left_over != NULL) { + *left_over = remainder; + } + return ; + } + } + } + panic("calculate_slab_over: failed."); +} + +// getorder - find order, should satisfy n <= minest 2^order +static inline size_t +getorder(size_t n) { + size_t order = MIN_SIZE_ORDER, order_size = (1 << order); + for (; order <= MAX_SIZE_ORDER; order ++, order_size <<= 1) { + if (n <= order_size) { + return order; + } + } + panic("getorder failed. %d\n", n); +} + +// init_kmem_cache - initial a slab_cache cachep according to the obj with the size = objsize +static void +init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align) { + list_init(&(cachep->slabs_full)); + list_init(&(cachep->slabs_notfull)); + + objsize = ROUNDUP(objsize, align); + cachep->objsize = objsize; + cachep->off_slab = (objsize >= (PGSIZE >> 3)); + + size_t left_over; + calculate_slab_order(cachep, objsize, align, cachep->off_slab, &left_over); + + assert(cachep->num > 0); + + size_t mgmt_size = slab_mgmt_size(cachep->num, align); + + if (cachep->off_slab && left_over >= mgmt_size) { + cachep->off_slab = 0; + } + + if (cachep->off_slab) { + cachep->offset = 0; + cachep->slab_cachep = slab_cache + (getorder(mgmt_size) - MIN_SIZE_ORDER); + } + else { + cachep->offset = mgmt_size; + } +} + +static void *kmem_cache_alloc(kmem_cache_t *cachep); + +#define slab_bufctl(slabp) \ + ((kmem_bufctl_t*)(((slab_t *)(slabp)) + 1)) + +// kmem_cache_slabmgmt - get the address of a slab according to page +// - and initialize the slab according to cachep +static slab_t * +kmem_cache_slabmgmt(kmem_cache_t *cachep, struct Page *page) { + void *objp = page2kva(page); + slab_t *slabp; + if (cachep->off_slab) { + if ((slabp = kmem_cache_alloc(cachep->slab_cachep)) == NULL) { + return NULL; + } + } + else { + slabp = page2kva(page); + } + slabp->inuse = 0; + slabp->offset = cachep->offset; + slabp->s_mem = objp + cachep->offset; + return slabp; +} + +#define SET_PAGE_CACHE(page, cachep) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + kmem_cache_t **__cachepp = (kmem_cache_t **)&(__page->page_link.next); \ + *__cachepp = (kmem_cache_t *)(cachep); \ + } while (0) + +#define SET_PAGE_SLAB(page, slabp) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + slab_t **__cachepp = (slab_t **)&(__page->page_link.prev); \ + *__cachepp = (slab_t *)(slabp); \ + } while (0) + +// kmem_cache_grow - allocate a new slab by calling alloc_pages +// - set control area in the new slab +static bool +kmem_cache_grow(kmem_cache_t *cachep) { + struct Page *page = alloc_pages(1 << cachep->page_order); + if (page == NULL) { + goto failed; + } + + slab_t *slabp; + if ((slabp = kmem_cache_slabmgmt(cachep, page)) == NULL) { + goto oops; + } + + size_t order_size = (1 << cachep->page_order); + do { + //setup this page in the free list (see memlayout.h: struct page)??? + SET_PAGE_CACHE(page, cachep); + SET_PAGE_SLAB(page, slabp); + //this page is used for slab + SetPageSlab(page); + page ++; + } while (-- order_size); + + int i; + for (i = 0; i < cachep->num; i ++) { + slab_bufctl(slabp)[i] = i + 1; + } + slab_bufctl(slabp)[cachep->num - 1] = BUFCTL_END; + slabp->free = 0; + + bool intr_flag; + local_intr_save(intr_flag); + { + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } + local_intr_restore(intr_flag); + return 1; + +oops: + free_pages(page, 1 << cachep->page_order); +failed: + return 0; +} + +// kmem_cache_alloc_one - allocate a obj in a slab +static void * +kmem_cache_alloc_one(kmem_cache_t *cachep, slab_t *slabp) { + slabp->inuse ++; + void *objp = slabp->s_mem + slabp->free * cachep->objsize; + slabp->free = slab_bufctl(slabp)[slabp->free]; + + if (slabp->free == BUFCTL_END) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_full), &(slabp->slab_link)); + } + return objp; +} + +// kmem_cache_alloc - call kmem_cache_alloc_one function to allocate a obj +// - if no free obj, try to allocate a slab +static void * +kmem_cache_alloc(kmem_cache_t *cachep) { + void *objp; + bool intr_flag; + +try_again: + local_intr_save(intr_flag); + if (list_empty(&(cachep->slabs_notfull))) { + goto alloc_new_slab; + } + slab_t *slabp = le2slab(list_next(&(cachep->slabs_notfull)), slab_link); + objp = kmem_cache_alloc_one(cachep, slabp); + local_intr_restore(intr_flag); + return objp; + +alloc_new_slab: + local_intr_restore(intr_flag); + + if (kmem_cache_grow(cachep)) { + goto try_again; + } + return NULL; +} + +// kmalloc - simple interface used by outside functions +// - to allocate a free memory using kmem_cache_alloc function +void * +kmalloc(size_t size) { + assert(size > 0); + size_t order = getorder(size); + if (order > MAX_SIZE_ORDER) { + return NULL; + } + return kmem_cache_alloc(slab_cache + (order - MIN_SIZE_ORDER)); +} + +static void kmem_cache_free(kmem_cache_t *cachep, void *obj); + +// kmem_slab_destroy - call free_pages & kmem_cache_free to free a slab +static void +kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) { + struct Page *page = kva2page(slabp->s_mem - slabp->offset); + + struct Page *p = page; + size_t order_size = (1 << cachep->page_order); + do { + assert(PageSlab(p)); + ClearPageSlab(p); + p ++; + } while (-- order_size); + + free_pages(page, 1 << cachep->page_order); + + if (cachep->off_slab) { + kmem_cache_free(cachep->slab_cachep, slabp); + } +} + +// kmem_cache_free_one - free an obj in a slab +// - if slab->inuse==0, then free the slab +static void +kmem_cache_free_one(kmem_cache_t *cachep, slab_t *slabp, void *objp) { + //should not use divide operator ??? + size_t objnr = (objp - slabp->s_mem) / cachep->objsize; + slab_bufctl(slabp)[objnr] = slabp->free; + slabp->free = objnr; + + slabp->inuse --; + + if (slabp->inuse == 0) { + list_del(&(slabp->slab_link)); + kmem_slab_destroy(cachep, slabp); + } + else if (slabp->inuse == cachep->num -1 ) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } +} + +#define GET_PAGE_CACHE(page) \ + (kmem_cache_t *)((page)->page_link.next) + +#define GET_PAGE_SLAB(page) \ + (slab_t *)((page)->page_link.prev) + +// kmem_cache_free - call kmem_cache_free_one function to free an obj +static void +kmem_cache_free(kmem_cache_t *cachep, void *objp) { + bool intr_flag; + struct Page *page = kva2page(objp); + + if (!PageSlab(page)) { + panic("not a slab page %08x\n", objp); + } + local_intr_save(intr_flag); + { + kmem_cache_free_one(cachep, GET_PAGE_SLAB(page), objp); + } + local_intr_restore(intr_flag); +} + +// kfree - simple interface used by ooutside functions to free an obj +void +kfree(void *objp) { + kmem_cache_free(GET_PAGE_CACHE(kva2page(objp)), objp); +} + +static inline void +check_slab_empty(void) { + int i; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + assert(list_empty(&(cachep->slabs_full))); + assert(list_empty(&(cachep->slabs_notfull))); + } +} + +void +check_slab(void) { + int i; + void *v0, *v1; + + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = slab_allocated(); + + /* slab must be empty now */ + check_slab_empty(); + assert(slab_allocated() == 0); + + kmem_cache_t *cachep0, *cachep1; + + cachep0 = slab_cache; + assert(cachep0->objsize == 32 && cachep0->num > 1 && !cachep0->off_slab); + assert((v0 = kmalloc(16)) != NULL); + + slab_t *slabp0, *slabp1; + + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + assert(slabp0->inuse == 1 && list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + struct Page *p0, *p1; + size_t order_size; + + + p0 = kva2page(slabp0->s_mem - slabp0->offset), p1 = p0; + order_size = (1 << cachep0->page_order); + for (i = 0; i < cachep0->page_order; i ++, p1 ++) { + assert(PageSlab(p1)); + assert(GET_PAGE_CACHE(p1) == cachep0 && GET_PAGE_SLAB(p1) == slabp0); + } + + assert(v0 == slabp0->s_mem); + assert((v1 = kmalloc(16)) != NULL && v1 == v0 + 32); + + kfree(v0); + assert(slabp0->free == 0); + kfree(v1); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->page_order; i ++, p0 ++) { + assert(!PageSlab(p0)); + } + + + v0 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + for (i = 0; i < cachep0->num - 1; i ++) { + kmalloc(16); + } + + assert(slabp0->inuse == cachep0->num); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + v1 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + kfree(v0); + assert(list_empty(&(cachep0->slabs_full))); + assert(list_next(&(slabp0->slab_link)) == &(slabp1->slab_link) + || list_next(&(slabp1->slab_link)) == &(slabp0->slab_link)); + + kfree(v1); + assert(!list_empty(&(cachep0->slabs_notfull))); + assert(list_next(&(cachep0->slabs_notfull)) == &(slabp0->slab_link)); + assert(list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + v1 = kmalloc(16); + assert(v1 == v0); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->num; i ++) { + kfree(v1 + i * cachep0->objsize); + } + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + cachep0 = slab_cache; + + bool has_off_slab = 0; + for (i = 0; i < SLAB_CACHE_NUM; i ++, cachep0 ++) { + if (cachep0->off_slab) { + has_off_slab = 1; + cachep1 = cachep0->slab_cachep; + if (!cachep1->off_slab) { + break; + } + } + } + + if (!has_off_slab) { + goto check_pass; + } + + assert(cachep0->off_slab && !cachep1->off_slab); + assert(cachep1 < cachep0); + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + assert(list_empty(&(cachep1->slabs_full))); + assert(list_empty(&(cachep1->slabs_notfull))); + + v0 = kmalloc(cachep0->objsize); + p0 = kva2page(v0); + assert(page2kva(p0) == v0); + + if (cachep0->num == 1) { + assert(!list_empty(&(cachep0->slabs_full))); + slabp0 = le2slab(list_next(&(cachep0->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + } + + assert(slabp0 != NULL); + + if (cachep1->num == 1) { + assert(!list_empty(&(cachep1->slabs_full))); + slabp1 = le2slab(list_next(&(cachep1->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep1->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep1->slabs_notfull)), slab_link); + } + + assert(slabp1 != NULL); + + order_size = (1 << cachep0->page_order); + for (i = 0; i < order_size; i ++, p0 ++) { + assert(PageSlab(p0)); + assert(GET_PAGE_CACHE(p0) == cachep0 && GET_PAGE_SLAB(p0) == slabp0); + } + + kfree(v0); + +check_pass: + + check_rb_tree(); + check_slab_empty(); + assert(slab_allocated() == 0); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == slab_allocated()); + + cprintf("check_slab() succeeded!\n"); +} + diff --git a/code/lab4/kern/mm/kmalloc.h b/code/lab4/kern/mm/kmalloc.h new file mode 100644 index 0000000..26c4b5d --- /dev/null +++ b/code/lab4/kern/mm/kmalloc.h @@ -0,0 +1,14 @@ +#ifndef __KERN_MM_SLAB_H__ +#define __KERN_MM_SLAB_H__ + +#include + +#define KMALLOC_MAX_ORDER 10 + +void kmalloc_init(void); + +void *kmalloc(size_t n); +void kfree(void *objp); + +#endif /* !__KERN_MM_SLAB_H__ */ + diff --git a/code/lab4/kern/mm/memlayout.h b/code/lab4/kern/mm/memlayout.h new file mode 100644 index 0000000..26fbe9f --- /dev/null +++ b/code/lab4/kern/mm/memlayout.h @@ -0,0 +1,140 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | | + * | | + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; +typedef pte_t swap_entry_t; //the pte can also be a swap entry + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // used in buddy system, stores the order (the X in 2^X) of the continuous memory block + int zone_num; // used in buddy system, the No. of zone which the page belongs to + list_entry_t page_link; // free list link + list_entry_t pra_page_link; // used for pra (page replace algorithm) + uintptr_t pra_vaddr; // used for pra (page replace algorithm) +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +/* for slab style kmalloc */ +#define PG_slab 2 // page frame is included in a slab +#define SetPageSlab(page) set_bit(PG_slab, &((page)->flags)) +#define ClearPageSlab(page) clear_bit(PG_slab, &((page)->flags)) +#define PageSlab(page) test_bit(PG_slab, &((page)->flags)) + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab4/kern/mm/mmu.h b/code/lab4/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab4/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab4/kern/mm/pmm.c b/code/lab4/kern/mm/pmm.c new file mode 100644 index 0000000..543e5e9 --- /dev/null +++ b/code/lab4/kern/mm/pmm.c @@ -0,0 +1,665 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + + while (1) + { + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + + if (page != NULL || n > 1 || swap_init_ok == 0) break; + + extern struct mm_struct *check_mm_struct; + //cprintf("page %x, call swap_out in alloc_pages %d\n",page, n); + swap_out(check_mm_struct, n, 0); + } + //cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages)); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + + kmalloc_init(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +// pgdir_alloc_page - call alloc_page & page_insert functions to +// - allocate a page size memory & setup an addr map +// - pa<->la with linear address la and the PDT pgdir +struct Page * +pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { + struct Page *page = alloc_page(); + if (page != NULL) { + if (page_insert(pgdir, page, la, perm) != 0) { + free_page(page); + return NULL; + } + if (swap_init_ok){ + swap_map_swappable(check_mm_struct, la, page, 0); + page->pra_vaddr=la; + assert(page_ref(page) == 1); + //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); + } + + } + + return page; +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} diff --git a/code/lab4/kern/mm/pmm.h b/code/lab4/kern/mm/pmm.h new file mode 100644 index 0000000..8b0a9f2 --- /dev/null +++ b/code/lab4/kern/mm/pmm.h @@ -0,0 +1,146 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +/* fork flags used in do_fork*/ +#define CLONE_VM 0x00000100 // set if VM shared between processes +#define CLONE_THREAD 0x00000200 // thread group + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); +struct Page *pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab4/kern/mm/swap.c b/code/lab4/kern/mm/swap.c new file mode 100644 index 0000000..6d05711 --- /dev/null +++ b/code/lab4/kern/mm/swap.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1 +#define CHECK_VALID_VIR_PAGE_NUM 5 +#define BEING_CHECK_VALID_VADDR 0X1000 +#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000 +// the max number of valid physical page for check +#define CHECK_VALID_PHY_PAGE_NUM 4 +// the max access seq number +#define MAX_SEQ_NO 10 + +static struct swap_manager *sm; +size_t max_swap_offset; + +volatile int swap_init_ok = 0; + +unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM]; + +unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO]; + +static void check_swap(void); + +int +swap_init(void) +{ + swapfs_init(); + + if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) + { + panic("bad max_swap_offset %08x.\n", max_swap_offset); + } + + + sm = &swap_manager_fifo; + int r = sm->init(); + + if (r == 0) + { + swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + check_swap(); + } + + return r; +} + +int +swap_init_mm(struct mm_struct *mm) +{ + return sm->init_mm(mm); +} + +int +swap_tick_event(struct mm_struct *mm) +{ + return sm->tick_event(mm); +} + +int +swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + return sm->map_swappable(mm, addr, page, swap_in); +} + +int +swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return sm->set_unswappable(mm, addr); +} + +volatile unsigned int swap_out_num=0; + +int +swap_out(struct mm_struct *mm, int n, int in_tick) +{ + int i; + for (i = 0; i != n; ++ i) + { + uintptr_t v; + //struct Page **ptr_page=NULL; + struct Page *page; + // cprintf("i %d, SWAP: call swap_out_victim\n",i); + int r = sm->swap_out_victim(mm, &page, in_tick); + if (r != 0) { + cprintf("i %d, swap_out: call swap_out_victim failed\n",i); + break; + } + assert(!PageReserved(page)); + + //cprintf("SWAP: choose victim page 0x%08x\n", page); + + v=page->pra_vaddr; + pte_t *ptep = get_pte(mm->pgdir, v, 0); + assert((*ptep & PTE_P) != 0); + + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { + cprintf("SWAP: failed to save\n"); + sm->map_swappable(mm, v, page, 0); + continue; + } + else { + cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; + free_page(page); + } + + tlb_invalidate(mm->pgdir, v); + } + return i; +} + +int +swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) +{ + struct Page *result = alloc_page(); + assert(result!=NULL); + + pte_t *ptep = get_pte(mm->pgdir, addr, 0); + // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); + + int r; + if ((r = swapfs_read((*ptep), result)) != 0) + { + assert(r!=0); + } + cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr); + *ptr_result=result; + return 0; +} + + + +static inline void +check_content_set(void) +{ + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x1010 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x2010 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x3010 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + *(unsigned char *)0x4010 = 0x0d; + assert(pgfault_num==4); +} + +static inline int +check_content_access(void) +{ + int ret = sm->check_swap(); + return ret; +} + +struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM]; +pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM]; +unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM]; + +extern free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +check_swap(void) +{ + //backup mem env + int ret, count = 0, total = 0, i; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + //assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + + //now we set the phy pages env + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + extern struct mm_struct *check_mm_struct; + assert(check_mm_struct == NULL); + + check_mm_struct = mm; + + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + //setup the temp Page Table vaddr 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + pte_t *temp_ptep=NULL; + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); + assert(temp_ptep!= NULL); + cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + + for (i=0;iphy_page environment for page relpacement algorithm + + + pgfault_num=0; + + check_content_set(); + assert( nr_free == 0); + for(i = 0; iproperty; + } + + assert(count == 0); + + cprintf("check_swap() succeeded!\n"); +} diff --git a/code/lab4/kern/mm/swap.h b/code/lab4/kern/mm/swap.h new file mode 100644 index 0000000..5d4aea8 --- /dev/null +++ b/code/lab4/kern/mm/swap.h @@ -0,0 +1,65 @@ +#ifndef __KERN_MM_SWAP_H__ +#define __KERN_MM_SWAP_H__ + +#include +#include +#include +#include + +/* * + * swap_entry_t + * -------------------------------------------- + * | offset | reserved | 0 | + * -------------------------------------------- + * 24 bits 7 bits 1 bit + * */ + +#define MAX_SWAP_OFFSET_LIMIT (1 << 24) + +extern size_t max_swap_offset; + +/* * + * swap_offset - takes a swap_entry (saved in pte), and returns + * the corresponding offset in swap mem_map. + * */ +#define swap_offset(entry) ({ \ + size_t __offset = (entry >> 8); \ + if (!(__offset > 0 && __offset < max_swap_offset)) { \ + panic("invalid swap_entry_t = %08x.\n", entry); \ + } \ + __offset; \ + }) + +struct swap_manager +{ + const char *name; + /* Global initialization for the swap manager */ + int (*init) (void); + /* Initialize the priv data inside mm_struct */ + int (*init_mm) (struct mm_struct *mm); + /* Called when tick interrupt occured */ + int (*tick_event) (struct mm_struct *mm); + /* Called when map a swappable page into the mm_struct */ + int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); + /* When a page is marked as shared, this routine is called to + * delete the addr entry from the swap manager */ + int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); + /* Try to swap out a page, return then victim */ + int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); + /* check the page relpacement algorithm */ + int (*check_swap)(void); +}; + +extern volatile int swap_init_ok; +int swap_init(void); +int swap_init_mm(struct mm_struct *mm); +int swap_tick_event(struct mm_struct *mm); +int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); +int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr); +int swap_out(struct mm_struct *mm, int n, int in_tick); +int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result); + +//#define MEMBER_OFFSET(m,t) ((int)(&((t *)0)->m)) +//#define FROM_MEMBER(m,t,a) ((t *)((char *)(a) - MEMBER_OFFSET(m,t))) + +#endif diff --git a/code/lab4/kern/mm/swap_fifo.c b/code/lab4/kern/mm/swap_fifo.c new file mode 100644 index 0000000..4cb00c1 --- /dev/null +++ b/code/lab4/kern/mm/swap_fifo.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out + * page replacement algorithm is a low-overhead algorithm that requires little book-keeping on + * the part of the operating system. The idea is obvious from the name - the operating system + * keeps track of all the pages in memory in a queue, with the most recent arrival at the back, + * and the earliest arrival in front. When a page needs to be replaced, the page at the front + * of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs + * poorly in practical application. Thus, it is rarely used in its unmodified form. This + * algorithm experiences Belady's anomaly. + * + * Details of FIFO PRA + * (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can + * link these pages into pra_list_head according the time order. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list + * implementation. You should know howto USE: list_init, list_add(list_add_after), + * list_add_before, list_del, list_next, list_prev. Another tricky method is to transform + * a general list struct to a special struct (such as struct page). You can find some MACRO: + * le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc. + */ + +list_entry_t pra_list_head; +/* + * (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head. + * Now, From the memory control struct mm_struct, we can access FIFO PRA + */ +static int +_fifo_init_mm(struct mm_struct *mm) +{ + list_init(&pra_list_head); + mm->sm_priv = &pra_list_head; + //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + return 0; +} +/* + * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue + */ +static int +_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + list_entry_t *entry=&(page->pra_page_link); + + assert(entry != NULL && head != NULL); + //record the page access situlation + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + return 0; +} +/* + * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, + * then set the addr of addr of this page to ptr_page. + */ +static int +_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + assert(head != NULL); + assert(in_tick==0); + /* Select the victim */ + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1) unlink the earliest arrival page in front of pra_list_head qeueue + //(2) set the addr of addr of this page to ptr_page + return 0; +} + +static int +_fifo_check_swap(void) { + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==4); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==4); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==4); + cprintf("write Virt Page e in fifo_check_swap\n"); + *(unsigned char *)0x5000 = 0x0e; + assert(pgfault_num==5); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==5); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==6); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==7); + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==8); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==9); + return 0; +} + + +static int +_fifo_init(void) +{ + return 0; +} + +static int +_fifo_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return 0; +} + +static int +_fifo_tick_event(struct mm_struct *mm) +{ return 0; } + + +struct swap_manager swap_manager_fifo = +{ + .name = "fifo swap manager", + .init = &_fifo_init, + .init_mm = &_fifo_init_mm, + .tick_event = &_fifo_tick_event, + .map_swappable = &_fifo_map_swappable, + .set_unswappable = &_fifo_set_unswappable, + .swap_out_victim = &_fifo_swap_out_victim, + .check_swap = &_fifo_check_swap, +}; diff --git a/code/lab4/kern/mm/swap_fifo.h b/code/lab4/kern/mm/swap_fifo.h new file mode 100644 index 0000000..1d74269 --- /dev/null +++ b/code/lab4/kern/mm/swap_fifo.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_SWAP_FIFO_H__ +#define __KERN_MM_SWAP_FIFO_H__ + +#include +extern struct swap_manager swap_manager_fifo; + +#endif diff --git a/code/lab4/kern/mm/vmm.c b/code/lab4/kern/mm/vmm.c new file mode 100644 index 0000000..490e530 --- /dev/null +++ b/code/lab4/kern/mm/vmm.c @@ -0,0 +1,390 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + vmm design include two parts: mm_struct (mm) & vma_struct (vma) + mm is the memory manager for the set of continuous virtual memory + area which have the same PDT. vma is a continuous virtual memory area. + There a linear link list for vma & a redblack link list for vma in mm. +--------------- + mm related functions: + golbal functions + struct mm_struct * mm_create(void) + void mm_destroy(struct mm_struct *mm) + int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) +-------------- + vma related functions: + global functions + struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) + void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) + struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) + local functions + inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) +--------------- + check correctness functions + void check_vmm(void); + void check_vma_struct(void); + void check_pgfault(void); +*/ + +static void check_vmm(void); +static void check_vma_struct(void); +static void check_pgfault(void); + +// mm_create - alloc a mm_struct & initialize it. +struct mm_struct * +mm_create(void) { + struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); + + if (mm != NULL) { + list_init(&(mm->mmap_list)); + mm->mmap_cache = NULL; + mm->pgdir = NULL; + mm->map_count = 0; + + if (swap_init_ok) swap_init_mm(mm); + else mm->sm_priv = NULL; + } + return mm; +} + +// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +struct vma_struct * +vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); + + if (vma != NULL) { + vma->vm_start = vm_start; + vma->vm_end = vm_end; + vma->vm_flags = vm_flags; + } + return vma; +} + + +// find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +struct vma_struct * +find_vma(struct mm_struct *mm, uintptr_t addr) { + struct vma_struct *vma = NULL; + if (mm != NULL) { + vma = mm->mmap_cache; + if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { + bool found = 0; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + vma = le2vma(le, list_link); + if (addr < vma->vm_end) { + found = 1; + break; + } + } + if (!found) { + vma = NULL; + } + } + if (vma != NULL) { + mm->mmap_cache = vma; + } + } + return vma; +} + + +// check_vma_overlap - check if vma1 overlaps vma2 ? +static inline void +check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { + assert(prev->vm_start < prev->vm_end); + assert(prev->vm_end <= next->vm_start); + assert(next->vm_start < next->vm_end); +} + + +// insert_vma_struct -insert vma in mm's list link +void +insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + assert(vma->vm_start < vma->vm_end); + list_entry_t *list = &(mm->mmap_list); + list_entry_t *le_prev = list, *le_next; + + list_entry_t *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *mmap_prev = le2vma(le, list_link); + if (mmap_prev->vm_start > vma->vm_start) { + break; + } + le_prev = le; + } + + le_next = list_next(le_prev); + + /* check overlap */ + if (le_prev != list) { + check_vma_overlap(le2vma(le_prev, list_link), vma); + } + if (le_next != list) { + check_vma_overlap(vma, le2vma(le_next, list_link)); + } + + vma->vm_mm = mm; + list_add_after(le_prev, &(vma->list_link)); + + mm->map_count ++; +} + +// mm_destroy - free mm and mm internal fields +void +mm_destroy(struct mm_struct *mm) { + + list_entry_t *list = &(mm->mmap_list), *le; + while ((le = list_next(list)) != list) { + list_del(le); + kfree(le2vma(le, list_link)); //kfree vma + } + kfree(mm); //kfree mm + mm=NULL; +} + +// vmm_init - initialize virtual memory management +// - now just call check_vmm to check correctness of vmm +void +vmm_init(void) { + check_vmm(); +} + +// check_vmm - check correctness of vmm +static void +check_vmm(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_vma_struct(); + check_pgfault(); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vmm() succeeded.\n"); +} + +static void +check_vma_struct(void) { + size_t nr_free_pages_store = nr_free_pages(); + + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + int step1 = 10, step2 = step1 * 10; + + int i; + for (i = step1; i >= 0; i --) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + for (i = step1 + 1; i <= step2; i ++) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + list_entry_t *le = list_next(&(mm->mmap_list)); + + for (i = 0; i <= step2; i ++) { + assert(le != &(mm->mmap_list)); + struct vma_struct *mmap = le2vma(le, list_link); + assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); + le = list_next(le); + } + + for (i = 0; i < 5 * step2 + 2; i ++) { + struct vma_struct *vma = find_vma(mm, i); + assert(vma != NULL); + int j = i / 5; + if (i >= 5 * j + 2) { + j ++; + } + assert(vma->vm_start == j * 5 && vma->vm_end == j * 5 + 2); + } + + mm_destroy(mm); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vma_struct() succeeded!\n"); +} + +struct mm_struct *check_mm_struct; + +// check_pgfault - check correctness of pgfault handler +static void +check_pgfault(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_mm_struct = mm_create(); + assert(check_mm_struct != NULL); + + struct mm_struct *mm = check_mm_struct; + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + uintptr_t addr = 0x100; + assert(find_vma(mm, addr) == vma); + + int i, sum = 0; + for (i = 0; i < 100; i ++) { + *(char *)(addr + i) = i; + sum += i; + } + for (i = 0; i < 100; i ++) { + sum -= *(char *)(addr + i); + } + assert(sum == 0); + + page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + free_page(pa2page(pgdir[0])); + pgdir[0] = 0; + + mm->pgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_pgfault() succeeded!\n"); +} +//page fault number +volatile unsigned int pgfault_num=0; + +/* do_pgfault - interrupt handler to process the page fault execption + * @mm : the control struct for a set of vma using the same PDT + * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware + * @addr : the addr which causes a memory access exception, (the contents of the CR2 register) + * + * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault + * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing + * the exception and recovering from it. + * (1) The contents of the CR2 register. The processor loads the CR2 register with the + * 32-bit linear address that generated the exception. The do_pgfault fun can + * use this address to locate the corresponding page directory and page-table + * entries. + * (2) An error code on the kernel stack. The error code for a page fault has a format different from + * that for other exceptions. The error code tells the exception handler three things: + * -- The P flag (bit 0) indicates whether the exception was due to a not-present page (0) + * or to either an access rights violation or the use of a reserved bit (1). + * -- The W/R flag (bit 1) indicates whether the memory access that caused the exception + * was a read (0) or write (1). + * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) + * or supervisor mode (0) at the time of the exception. + */ +int +do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { + int ret = -E_INVAL; + //try to find a vma which include addr + struct vma_struct *vma = find_vma(mm, addr); + + pgfault_num++; + //If the addr is in the range of a mm's vma? + if (vma == NULL || vma->vm_start > addr) { + cprintf("not valid addr %x, and can not find it in vma\n", addr); + goto failed; + } + //check the error_code + switch (error_code & 3) { + default: + /* error code flag : default is 3 ( W/R=1, P=1): write, present */ + case 2: /* error code flag : (W/R=1, P=0): write, not present */ + if (!(vma->vm_flags & VM_WRITE)) { + cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); + goto failed; + } + break; + case 1: /* error code flag : (W/R=0, P=1): read, present */ + cprintf("do_pgfault failed: error code flag = read AND present\n"); + goto failed; + case 0: /* error code flag : (W/R=0, P=0): read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { + cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); + goto failed; + } + } + /* IF (write an existed addr ) OR + * (write an non_existed addr && addr is writable) OR + * (read an non_existed addr && addr is readable) + * THEN + * continue process + */ + uint32_t perm = PTE_U; + if (vma->vm_flags & VM_WRITE) { + perm |= PTE_W; + } + addr = ROUNDDOWN(addr, PGSIZE); + + ret = -E_NO_MEM; + + pte_t *ptep=NULL; + /*LAB3 EXERCISE 1: YOUR CODE + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * get_pte : get an pte and return the kernel virtual address of this pte for la + * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') + * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup + * an addr map pa<--->la with linear address la and the PDT pgdir + * DEFINES: + * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + * VARIABLES: + * mm->pgdir : the PDT of these vma + * + */ +#if 0 + /*LAB3 EXERCISE 1: YOUR CODE*/ + ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + if (*ptep == 0) { + //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + + } + else { + /*LAB3 EXERCISE 2: YOUR CODE + * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, + * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, + * find the addr of disk page, read the content of disk page into this memroy page + * page_insert : build the map of phy addr of an Page with the linear addr la + * swap_map_swappable : set the page swappable + */ + if(swap_init_ok) { + struct Page *page=NULL; + //(1)According to the mm AND addr, try to load the content of right disk page + // into the memory which page managed. + //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr + //(3) make the page swappable. + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed; + } + } +#endif + ret = 0; +failed: + return ret; +} + diff --git a/code/lab4/kern/mm/vmm.h b/code/lab4/kern/mm/vmm.h new file mode 100644 index 0000000..6b93b6a --- /dev/null +++ b/code/lab4/kern/mm/vmm.h @@ -0,0 +1,51 @@ +#ifndef __KERN_MM_VMM_H__ +#define __KERN_MM_VMM_H__ + +#include +#include +#include +#include + +//pre define +struct mm_struct; + +// the virtual continuous memory area(vma) +struct vma_struct { + struct mm_struct *vm_mm; // the set of vma using the same PDT + uintptr_t vm_start; // start addr of vma + uintptr_t vm_end; // end addr of vma + uint32_t vm_flags; // flags of vma + list_entry_t list_link; // linear list link which sorted by start addr of vma +}; + +#define le2vma(le, member) \ + to_struct((le), struct vma_struct, member) + +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 + +// the control struct for a set of vma using the same PDT +struct mm_struct { + list_entry_t mmap_list; // linear list link which sorted by start addr of vma + struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose + pde_t *pgdir; // the PDT of these vma + int map_count; // the count of these vma + void *sm_priv; // the private data for swap manager +}; + +struct vma_struct *find_vma(struct mm_struct *mm, uintptr_t addr); +struct vma_struct *vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags); +void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma); + +struct mm_struct *mm_create(void); +void mm_destroy(struct mm_struct *mm); + +void vmm_init(void); + +int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr); + +extern volatile unsigned int pgfault_num; +extern struct mm_struct *check_mm_struct; +#endif /* !__KERN_MM_VMM_H__ */ + diff --git a/code/lab4/kern/process/entry.S b/code/lab4/kern/process/entry.S new file mode 100644 index 0000000..7482e23 --- /dev/null +++ b/code/lab4/kern/process/entry.S @@ -0,0 +1,10 @@ +.text +.globl kernel_thread_entry +kernel_thread_entry: # void kernel_thread(void) + + pushl %edx # push arg + call *%ebx # call fn + + pushl %eax # save the return value of fn(arg) + call do_exit # call do_exit to terminate current thread + diff --git a/code/lab4/kern/process/proc.c b/code/lab4/kern/process/proc.c new file mode 100644 index 0000000..9c0b0e3 --- /dev/null +++ b/code/lab4/kern/process/proc.c @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------- process/thread mechanism design&implementation ------------- +(an simplified Linux process/thread mechanism ) +introduction: + ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads +for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to +manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory). +------------------------------ +process state : meaning -- reason + PROC_UNINIT : uninitialized -- alloc_proc + PROC_SLEEPING : sleeping -- try_free_pages, do_wait, do_sleep + PROC_RUNNABLE : runnable(maybe running) -- proc_init, wakeup_proc, + PROC_ZOMBIE : almost dead -- do_exit + +----------------------------- +process state changing: + + alloc_proc RUNNING + + +--<----<--+ + + + proc_run + + V +-->---->--+ +PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING -- + A + + + | +--- do_exit --> PROC_ZOMBIE + + + + + -----------------------wakeup_proc---------------------------------- +----------------------------- +process relations +parent: proc->parent (proc is children) +children: proc->cptr (proc is parent) +older sibling: proc->optr (proc is younger sibling) +younger sibling: proc->yptr (proc is older sibling) +----------------------------- +related syscall for process: +SYS_exit : process exit, -->do_exit +SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc +SYS_wait : wait process -->do_wait +SYS_exec : after fork, process execute a program -->load a program and refresh the mm +SYS_clone : create child thread -->do_fork-->wakeup_proc +SYS_yield : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process +SYS_sleep : process sleep -->do_sleep +SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING + -->wakeup_proc-->do_wait-->do_exit +SYS_getpid : get the process's pid + +*/ + +// the process set's list +list_entry_t proc_list; + +#define HASH_SHIFT 10 +#define HASH_LIST_SIZE (1 << HASH_SHIFT) +#define pid_hashfn(x) (hash32(x, HASH_SHIFT)) + +// has list for process set based on pid +static list_entry_t hash_list[HASH_LIST_SIZE]; + +// idle proc +struct proc_struct *idleproc = NULL; +// init proc +struct proc_struct *initproc = NULL; +// current proc +struct proc_struct *current = NULL; + +static int nr_process = 0; + +void kernel_thread_entry(void); +void forkrets(struct trapframe *tf); +void switch_to(struct context *from, struct context *to); + +// alloc_proc - alloc a proc_struct and init all fields of proc_struct +static struct proc_struct * +alloc_proc(void) { + struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); + if (proc != NULL) { + //LAB4:EXERCISE1 YOUR CODE + /* + * below fields in proc_struct need to be initialized + * enum proc_state state; // Process state + * int pid; // Process ID + * int runs; // the running times of Proces + * uintptr_t kstack; // Process kernel stack + * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + * struct proc_struct *parent; // the parent process + * struct mm_struct *mm; // Process's memory management field + * struct context context; // Switch here to run process + * struct trapframe *tf; // Trap frame for current interrupt + * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + * uint32_t flags; // Process flag + * char name[PROC_NAME_LEN + 1]; // Process name + */ + } + return proc; +} + +// set_proc_name - set the name of proc +char * +set_proc_name(struct proc_struct *proc, const char *name) { + memset(proc->name, 0, sizeof(proc->name)); + return memcpy(proc->name, name, PROC_NAME_LEN); +} + +// get_proc_name - get the name of proc +char * +get_proc_name(struct proc_struct *proc) { + static char name[PROC_NAME_LEN + 1]; + memset(name, 0, sizeof(name)); + return memcpy(name, proc->name, PROC_NAME_LEN); +} + +// get_pid - alloc a unique pid for process +static int +get_pid(void) { + static_assert(MAX_PID > MAX_PROCESS); + struct proc_struct *proc; + list_entry_t *list = &proc_list, *le; + static int next_safe = MAX_PID, last_pid = MAX_PID; + if (++ last_pid >= MAX_PID) { + last_pid = 1; + goto inside; + } + if (last_pid >= next_safe) { + inside: + next_safe = MAX_PID; + repeat: + le = list; + while ((le = list_next(le)) != list) { + proc = le2proc(le, list_link); + if (proc->pid == last_pid) { + if (++ last_pid >= next_safe) { + if (last_pid >= MAX_PID) { + last_pid = 1; + } + next_safe = MAX_PID; + goto repeat; + } + } + else if (proc->pid > last_pid && next_safe > proc->pid) { + next_safe = proc->pid; + } + } + } + return last_pid; +} + +// proc_run - make process "proc" running on cpu +// NOTE: before call switch_to, should load base addr of "proc"'s new PDT +void +proc_run(struct proc_struct *proc) { + if (proc != current) { + bool intr_flag; + struct proc_struct *prev = current, *next = proc; + local_intr_save(intr_flag); + { + current = proc; + load_esp0(next->kstack + KSTACKSIZE); + lcr3(next->cr3); + switch_to(&(prev->context), &(next->context)); + } + local_intr_restore(intr_flag); + } +} + +// forkret -- the first kernel entry point of a new thread/process +// NOTE: the addr of forkret is setted in copy_thread function +// after switch_to, the current proc will execute here. +static void +forkret(void) { + forkrets(current->tf); +} + +// hash_proc - add proc into proc hash_list +static void +hash_proc(struct proc_struct *proc) { + list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); +} + +// find_proc - find proc frome proc hash_list according to pid +struct proc_struct * +find_proc(int pid) { + if (0 < pid && pid < MAX_PID) { + list_entry_t *list = hash_list + pid_hashfn(pid), *le = list; + while ((le = list_next(le)) != list) { + struct proc_struct *proc = le2proc(le, hash_link); + if (proc->pid == pid) { + return proc; + } + } + } + return NULL; +} + +// kernel_thread - create a kernel thread using "fn" function +// NOTE: the contents of temp trapframe tf will be copied to +// proc->tf in do_fork-->copy_thread function +int +kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { + struct trapframe tf; + memset(&tf, 0, sizeof(struct trapframe)); + tf.tf_cs = KERNEL_CS; + tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; + tf.tf_regs.reg_ebx = (uint32_t)fn; + tf.tf_regs.reg_edx = (uint32_t)arg; + tf.tf_eip = (uint32_t)kernel_thread_entry; + return do_fork(clone_flags | CLONE_VM, 0, &tf); +} + +// setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack +static int +setup_kstack(struct proc_struct *proc) { + struct Page *page = alloc_pages(KSTACKPAGE); + if (page != NULL) { + proc->kstack = (uintptr_t)page2kva(page); + return 0; + } + return -E_NO_MEM; +} + +// put_kstack - free the memory space of process kernel stack +static void +put_kstack(struct proc_struct *proc) { + free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE); +} + +// copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags +// - if clone_flags & CLONE_VM, then "share" ; else "duplicate" +static int +copy_mm(uint32_t clone_flags, struct proc_struct *proc) { + assert(current->mm == NULL); + /* do nothing in this project */ + return 0; +} + +// copy_thread - setup the trapframe on the process's kernel stack top and +// - setup the kernel entry point and stack of process +static void +copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { + proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; + *(proc->tf) = *tf; + proc->tf->tf_regs.reg_eax = 0; + proc->tf->tf_esp = esp; + proc->tf->tf_eflags |= FL_IF; + + proc->context.eip = (uintptr_t)forkret; + proc->context.esp = (uintptr_t)(proc->tf); +} + +/* do_fork - parent process for a new child process + * @clone_flags: used to guide how to clone the child process + * @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread. + * @tf: the trapframe info, which will be copied to child process's proc->tf + */ +int +do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { + int ret = -E_NO_FREE_PROC; + struct proc_struct *proc; + if (nr_process >= MAX_PROCESS) { + goto fork_out; + } + ret = -E_NO_MEM; + //LAB4:EXERCISE2 YOUR CODE + /* + * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * alloc_proc: create a proc struct and init fields (lab4:exercise1) + * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack + * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags + * if clone_flags & CLONE_VM, then "share" ; else "duplicate" + * copy_thread: setup the trapframe on the process's kernel stack top and + * setup the kernel entry point and stack of process + * hash_proc: add proc into proc hash_list + * get_pid: alloc a unique pid for process + * wakup_proc: set proc->state = PROC_RUNNABLE + * VARIABLES: + * proc_list: the process set's list + * nr_process: the number of process set + */ + + // 1. call alloc_proc to allocate a proc_struct + // 2. call setup_kstack to allocate a kernel stack for child process + // 3. call copy_mm to dup OR share mm according clone_flag + // 4. call copy_thread to setup tf & context in proc_struct + // 5. insert proc_struct into hash_list && proc_list + // 6. call wakup_proc to make the new child process RUNNABLE + // 7. set ret vaule using child proc's pid +fork_out: + return ret; + +bad_fork_cleanup_kstack: + put_kstack(proc); +bad_fork_cleanup_proc: + kfree(proc); + goto fork_out; +} + +// do_exit - called by sys_exit +// 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process +// 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself. +// 3. call scheduler to switch to other process +int +do_exit(int error_code) { + panic("process exit!!.\n"); +} + +// init_main - the second kernel thread used to create user_main kernel threads +static int +init_main(void *arg) { + cprintf("this initproc, pid = %d, name = \"%s\"\n", current->pid, get_proc_name(current)); + cprintf("To U: \"%s\".\n", (const char *)arg); + cprintf("To U: \"en.., Bye, Bye. :)\"\n"); + return 0; +} + +// proc_init - set up the first kernel thread idleproc "idle" by itself and +// - create the second kernel thread init_main +void +proc_init(void) { + int i; + + list_init(&proc_list); + for (i = 0; i < HASH_LIST_SIZE; i ++) { + list_init(hash_list + i); + } + + if ((idleproc = alloc_proc()) == NULL) { + panic("cannot alloc idleproc.\n"); + } + + idleproc->pid = 0; + idleproc->state = PROC_RUNNABLE; + idleproc->kstack = (uintptr_t)bootstack; + idleproc->need_resched = 1; + set_proc_name(idleproc, "idle"); + nr_process ++; + + current = idleproc; + + int pid = kernel_thread(init_main, "Hello world!!", 0); + if (pid <= 0) { + panic("create init_main failed.\n"); + } + + initproc = find_proc(pid); + set_proc_name(initproc, "init"); + + assert(idleproc != NULL && idleproc->pid == 0); + assert(initproc != NULL && initproc->pid == 1); +} + +// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works +void +cpu_idle(void) { + while (1) { + if (current->need_resched) { + schedule(); + } + } +} + diff --git a/code/lab4/kern/process/proc.h b/code/lab4/kern/process/proc.h new file mode 100644 index 0000000..1835f2b --- /dev/null +++ b/code/lab4/kern/process/proc.h @@ -0,0 +1,77 @@ +#ifndef __KERN_PROCESS_PROC_H__ +#define __KERN_PROCESS_PROC_H__ + +#include +#include +#include +#include + + +// process's state in his life cycle +enum proc_state { + PROC_UNINIT = 0, // uninitialized + PROC_SLEEPING, // sleeping + PROC_RUNNABLE, // runnable(maybe running) + PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource +}; + +// Saved registers for kernel context switches. +// Don't need to save all the %fs etc. segment registers, +// because they are constant across kernel contexts. +// Save all the regular registers so we don't need to care +// which are caller save, but not the return register %eax. +// (Not saving %eax just simplifies the switching code.) +// The layout of context must match code in switch.S. +struct context { + uint32_t eip; + uint32_t esp; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t esi; + uint32_t edi; + uint32_t ebp; +}; + +#define PROC_NAME_LEN 15 +#define MAX_PROCESS 4096 +#define MAX_PID (MAX_PROCESS * 2) + +extern list_entry_t proc_list; + +struct proc_struct { + enum proc_state state; // Process state + int pid; // Process ID + int runs; // the running times of Proces + uintptr_t kstack; // Process kernel stack + volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + struct proc_struct *parent; // the parent process + struct mm_struct *mm; // Process's memory management field + struct context context; // Switch here to run process + struct trapframe *tf; // Trap frame for current interrupt + uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + uint32_t flags; // Process flag + char name[PROC_NAME_LEN + 1]; // Process name + list_entry_t list_link; // Process link list + list_entry_t hash_link; // Process hash list +}; + +#define le2proc(le, member) \ + to_struct((le), struct proc_struct, member) + +extern struct proc_struct *idleproc, *initproc, *current; + +void proc_init(void); +void proc_run(struct proc_struct *proc); +int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags); + +char *set_proc_name(struct proc_struct *proc, const char *name); +char *get_proc_name(struct proc_struct *proc); +void cpu_idle(void) __attribute__((noreturn)); + +struct proc_struct *find_proc(int pid); +int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf); +int do_exit(int error_code); + +#endif /* !__KERN_PROCESS_PROC_H__ */ + diff --git a/code/lab4/kern/process/switch.S b/code/lab4/kern/process/switch.S new file mode 100644 index 0000000..27b4c8c --- /dev/null +++ b/code/lab4/kern/process/switch.S @@ -0,0 +1,30 @@ +.text +.globl switch_to +switch_to: # switch_to(from, to) + + # save from's registers + movl 4(%esp), %eax # eax points to from + popl 0(%eax) # save eip !popl + movl %esp, 4(%eax) + movl %ebx, 8(%eax) + movl %ecx, 12(%eax) + movl %edx, 16(%eax) + movl %esi, 20(%eax) + movl %edi, 24(%eax) + movl %ebp, 28(%eax) + + # restore to's registers + movl 4(%esp), %eax # not 8(%esp): popped return address already + # eax now points to to + movl 28(%eax), %ebp + movl 24(%eax), %edi + movl 20(%eax), %esi + movl 16(%eax), %edx + movl 12(%eax), %ecx + movl 8(%eax), %ebx + movl 4(%eax), %esp + + pushl 0(%eax) # push eip + + ret + diff --git a/code/lab4/kern/schedule/sched.c b/code/lab4/kern/schedule/sched.c new file mode 100644 index 0000000..8c8c1a8 --- /dev/null +++ b/code/lab4/kern/schedule/sched.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +void +wakeup_proc(struct proc_struct *proc) { + assert(proc->state != PROC_ZOMBIE && proc->state != PROC_RUNNABLE); + proc->state = PROC_RUNNABLE; +} + +void +schedule(void) { + bool intr_flag; + list_entry_t *le, *last; + struct proc_struct *next = NULL; + local_intr_save(intr_flag); + { + current->need_resched = 0; + last = (current == idleproc) ? &proc_list : &(current->list_link); + le = last; + do { + if ((le = list_next(le)) != &proc_list) { + next = le2proc(le, list_link); + if (next->state == PROC_RUNNABLE) { + break; + } + } + } while (le != last); + if (next == NULL || next->state != PROC_RUNNABLE) { + next = idleproc; + } + next->runs ++; + if (next != current) { + proc_run(next); + } + } + local_intr_restore(intr_flag); +} + diff --git a/code/lab4/kern/schedule/sched.h b/code/lab4/kern/schedule/sched.h new file mode 100644 index 0000000..ed1fc6e --- /dev/null +++ b/code/lab4/kern/schedule/sched.h @@ -0,0 +1,10 @@ +#ifndef __KERN_SCHEDULE_SCHED_H__ +#define __KERN_SCHEDULE_SCHED_H__ + +#include + +void schedule(void); +void wakeup_proc(struct proc_struct *proc); + +#endif /* !__KERN_SCHEDULE_SCHED_H__ */ + diff --git a/code/lab4/kern/sync/sync.h b/code/lab4/kern/sync/sync.h new file mode 100644 index 0000000..9653377 --- /dev/null +++ b/code/lab4/kern/sync/sync.h @@ -0,0 +1,28 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab4/kern/trap/trap.c b/code/lab4/kern/trap/trap.c new file mode 100644 index 0000000..90f266e --- /dev/null +++ b/code/lab4/kern/trap/trap.c @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static inline void +print_pgfault(struct trapframe *tf) { + /* error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * bit 2 == 0 means kernel, 1 means user + * */ + cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), + (tf->tf_err & 4) ? 'U' : 'K', + (tf->tf_err & 2) ? 'W' : 'R', + (tf->tf_err & 1) ? "protection fault" : "no page found"); +} + +static int +pgfault_handler(struct trapframe *tf) { + extern struct mm_struct *check_mm_struct; + print_pgfault(tf); + if (check_mm_struct != NULL) { + return do_pgfault(check_mm_struct, tf->tf_err, rcr2()); + } + panic("unhandled page fault.\n"); +} + +static volatile int in_swap_tick_event = 0; +extern struct mm_struct *check_mm_struct; + +static void +trap_dispatch(struct trapframe *tf) { + char c; + + int ret; + + switch (tf->tf_trapno) { + case T_PGFLT: //page fault + if ((ret = pgfault_handler(tf)) != 0) { + print_trapframe(tf); + panic("handle pgfault failed. %e\n", ret); + } + break; + case IRQ_OFFSET + IRQ_TIMER: +#if 0 + LAB3 : If some page replacement algorithm(such as CLOCK PRA) need tick to change the priority of pages, + then you can add code here. +#endif + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + // in kernel, it must be a mistake + if ((tf->tf_cs & 3) == 0) { + print_trapframe(tf); + panic("unexpected trap in kernel.\n"); + } + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + trap_dispatch(tf); +} + diff --git a/code/lab4/kern/trap/trap.h b/code/lab4/kern/trap/trap.h new file mode 100644 index 0000000..74d973d --- /dev/null +++ b/code/lab4/kern/trap/trap.h @@ -0,0 +1,91 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +#define T_SYSCALL 0x80 // SYSCALL, ONLY FOR THIS PROJ + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab4/kern/trap/trapentry.S b/code/lab4/kern/trap/trapentry.S new file mode 100644 index 0000000..3565ec8 --- /dev/null +++ b/code/lab4/kern/trap/trapentry.S @@ -0,0 +1,49 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + +.globl forkrets +forkrets: + # set stack to this new process's trapframe + movl 4(%esp), %esp + jmp __trapret diff --git a/code/lab4/kern/trap/vectors.S b/code/lab4/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab4/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab4/libs/atomic.h b/code/lab4/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab4/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab4/libs/defs.h b/code/lab4/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab4/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab4/libs/elf.h b/code/lab4/libs/elf.h new file mode 100644 index 0000000..bdfee3d --- /dev/null +++ b/code/lab4/libs/elf.h @@ -0,0 +1,40 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab4/libs/error.h b/code/lab4/libs/error.h new file mode 100644 index 0000000..b43fbd6 --- /dev/null +++ b/code/lab4/libs/error.h @@ -0,0 +1,16 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault + +/* the maximum allowed */ +#define MAXERROR 6 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab4/libs/hash.c b/code/lab4/libs/hash.c new file mode 100644 index 0000000..61bcd88 --- /dev/null +++ b/code/lab4/libs/hash.c @@ -0,0 +1,18 @@ +#include + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL + +/* * + * hash32 - generate a hash value in the range [0, 2^@bits - 1] + * @val: the input value + * @bits: the number of bits in a return value + * + * High bits are more random, so we use them. + * */ +uint32_t +hash32(uint32_t val, unsigned int bits) { + uint32_t hash = val * GOLDEN_RATIO_PRIME_32; + return (hash >> (32 - bits)); +} + diff --git a/code/lab4/libs/list.h b/code/lab4/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab4/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab4/libs/printfmt.c b/code/lab4/libs/printfmt.c new file mode 100644 index 0000000..93a1c47 --- /dev/null +++ b/code/lab4/libs/printfmt.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab4/libs/rand.c b/code/lab4/libs/rand.c new file mode 100644 index 0000000..2a2c4e7 --- /dev/null +++ b/code/lab4/libs/rand.c @@ -0,0 +1,26 @@ +#include +#include + +static unsigned long long next = 1; + +/* * + * rand - returns a pseudo-random integer + * + * The rand() function return a value in the range [0, RAND_MAX]. + * */ +int +rand(void) { + next = (next * 0x5DEECE66DLL + 0xBLL) & ((1LL << 48) - 1); + unsigned long long result = (next >> 12); + return (int)do_div(result, RAND_MAX + 1); +} + +/* * + * srand - seed the random number generator with the given number + * @seed: the required seed number + * */ +void +srand(unsigned int seed) { + next = seed; +} + diff --git a/code/lab4/libs/stdarg.h b/code/lab4/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab4/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab4/libs/stdio.h b/code/lab4/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab4/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab4/libs/stdlib.h b/code/lab4/libs/stdlib.h new file mode 100644 index 0000000..51878ef --- /dev/null +++ b/code/lab4/libs/stdlib.h @@ -0,0 +1,17 @@ +#ifndef __LIBS_STDLIB_H__ +#define __LIBS_STDLIB_H__ + +#include + +/* the largest number rand will return */ +#define RAND_MAX 2147483647UL + +/* libs/rand.c */ +int rand(void); +void srand(unsigned int seed); + +/* libs/hash.c */ +uint32_t hash32(uint32_t val, unsigned int bits); + +#endif /* !__LIBS_RAND_H__ */ + diff --git a/code/lab4/libs/string.c b/code/lab4/libs/string.c new file mode 100644 index 0000000..bcf1b1c --- /dev/null +++ b/code/lab4/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab4/libs/string.h b/code/lab4/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab4/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab4/libs/x86.h b/code/lab4/libs/x86.h new file mode 100644 index 0000000..b29f671 --- /dev/null +++ b/code/lab4/libs/x86.h @@ -0,0 +1,302 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab4/tools/boot.ld b/code/lab4/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab4/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab4/tools/function.mk b/code/lab4/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab4/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab4/tools/gdbinit b/code/lab4/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab4/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab4/tools/grade.sh b/code/lab4/tools/grade.sh new file mode 100644 index 0000000..473ca83 --- /dev/null +++ b/code/lab4/tools/grade.sh @@ -0,0 +1,351 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-Ddef...] [-check ] checkargs ... + tag= + check=check_regexps + while true; do + select= + case $1 in + -tag) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## set default qemu-options +qemuopts="-hda $osimg" + +## set break-function, default is readline +brkfun=readline + +## check now!! + +quick_run 'Check VMM' + +pts=5 +quick_check 'check pmm' \ + 'memory management: buddy_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' + +pts=5 +quick_check 'check page table' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' + +pts=10 +quick_check 'check slab' \ + 'check_slab() succeeded!' + +pts=25 +quick_check 'check vmm' \ + 'check_vma_struct() succeeded!' \ + 'page fault at 0x00000100: K/W [no page found].' \ + 'check_pgfault() succeeded!' \ + 'check_vmm() succeeded.' + +pts=5 +quick_check 'check ticks' \ + '++ setup timer interrupts' \ + '100 ticks' \ + 'End of Test.' + +## print final-score +show_final + diff --git a/code/lab4/tools/kernel.ld b/code/lab4/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab4/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab4/tools/sign.c b/code/lab4/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab4/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab4/tools/vector.c b/code/lab4/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab4/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab5/Makefile b/code/lab5/Makefile new file mode 100644 index 0000000..b1ccfce --- /dev/null +++ b/code/lab5/Makefile @@ -0,0 +1,323 @@ +PROJ := 5 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +GDB := $(GCCPREFIX)gdb + +CC ?= $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +USER_PREFIX := __user_ + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) +filename = $(basename $(notdir $(1))) +ubinfile = $(call outfile,$(addprefix $(USER_PREFIX),$(call filename,$(1)))) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# user programs + +UINCLUDE += user/include/ \ + user/libs/ + +USRCDIR += user + +ULIBDIR += user/libs + +UCFLAGS += $(addprefix -I,$(UINCLUDE)) +USER_BINS := + +$(call add_files_cc,$(call listf_cc,$(ULIBDIR)),ulibs,$(UCFLAGS)) +$(call add_files_cc,$(call listf_cc,$(USRCDIR)),uprog,$(UCFLAGS)) + +UOBJS := $(call read_packet,ulibs libs) + +define uprog_ld +__user_bin__ := $$(call ubinfile,$(1)) +USER_BINS += $$(__user_bin__) +$$(__user_bin__): tools/user.ld +$$(__user_bin__): $$(UOBJS) +$$(__user_bin__): $(1) | $$$$(dir $$$$@) + $(V)$(LD) $(LDFLAGS) -T tools/user.ld -o $$@ $$(UOBJS) $(1) + @$(OBJDUMP) -S $$@ > $$(call cgtype,$$<,o,asm) + @$(OBJDUMP) -t $$@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$$$/d' > $$(call cgtype,$$<,o,sym) +endef + +$(foreach p,$(call read_packet,uprog),$(eval $(call uprog_ld,$(p)))) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ \ + kern/fs/ \ + kern/process \ + kern/schedule \ + kern/syscall + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync \ + kern/fs \ + kern/process \ + kern/schedule \ + kern/syscall + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) $(USER_BINS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) -b binary $(USER_BINS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# ------------------------------------------------------------------- + +# create swap.img +SWAPIMG := $(call totarget,swap.img) + +$(SWAPIMG): + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + +$(call create_target,swap.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + run-.+ \ + build-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback + +.PHONY: qemu qemu-nox debug debug-nox +qemu: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +TERMINAL := gnome-terminal + +debug: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +RUN_PREFIX := _binary_$(OBJDIR)_$(USER_PREFIX) +MAKEOPTS := --quiet --no-print-directory + +run-%: build-% + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +build-%: touch + $(V)$(MAKE) $(MAKEOPTS) "DEFS+=-DTEST=$* -DTESTSTART=$(RUN_PREFIX)$*_out_start -DTESTSIZE=$(RUN_PREFIX)$*_out_size" + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := proj$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/process/proc.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab5/boot/asm.h b/code/lab5/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab5/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab5/boot/bootasm.S b/code/lab5/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab5/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab5/boot/bootmain.c b/code/lab5/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab5/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab5/kern/debug/assert.h b/code/lab5/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab5/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab5/kern/debug/kdebug.c b/code/lab5/kern/debug/kdebug.c new file mode 100644 index 0000000..fedbf5b --- /dev/null +++ b/code/lab5/kern/debug/kdebug.c @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* user STABS data structure */ +struct userstabdata { + const struct stab *stabs; + const struct stab *stab_end; + const char *stabstr; + const char *stabstr_end; +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + // find the relevant set of stabs + if (addr >= KERNBASE) { + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + } + else { + // user-program linker script, tools/user.ld puts the information about the + // program's stabs (included __STAB_BEGIN__, __STAB_END__, __STABSTR_BEGIN__, + // and __STABSTR_END__) in a structure located at virtual address USTAB. + const struct userstabdata *usd = (struct userstabdata *)USTAB; + + // make sure that debugger (current process) can access this memory + struct mm_struct *mm; + if (current == NULL || (mm = current->mm) == NULL) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)usd, sizeof(struct userstabdata), 0)) { + return -1; + } + + stabs = usd->stabs; + stab_end = usd->stab_end; + stabstr = usd->stabstr; + stabstr_end = usd->stabstr_end; + + // make sure the STABS and string table memory is valid + if (!user_mem_check(mm, (uintptr_t)stabs, (uintptr_t)stab_end - (uintptr_t)stabs, 0)) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)stabstr, stabstr_end - stabstr, 0)) { + return -1; + } + } + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab5/kern/debug/kdebug.h b/code/lab5/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab5/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab5/kern/debug/monitor.c b/code/lab5/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab5/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab5/kern/debug/monitor.h b/code/lab5/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab5/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab5/kern/debug/panic.c b/code/lab5/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab5/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab5/kern/debug/stab.h b/code/lab5/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab5/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab5/kern/driver/clock.c b/code/lab5/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab5/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab5/kern/driver/clock.h b/code/lab5/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab5/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab5/kern/driver/console.c b/code/lab5/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab5/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab5/kern/driver/console.h b/code/lab5/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab5/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab5/kern/driver/ide.c b/code/lab5/kern/driver/ide.c new file mode 100644 index 0000000..271cfa8 --- /dev/null +++ b/code/lab5/kern/driver/ide.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA_DATA 0x00 +#define ISA_ERROR 0x01 +#define ISA_PRECOMP 0x01 +#define ISA_CTRL 0x02 +#define ISA_SECCNT 0x02 +#define ISA_SECTOR 0x03 +#define ISA_CYL_LO 0x04 +#define ISA_CYL_HI 0x05 +#define ISA_SDH 0x06 +#define ISA_COMMAND 0x07 +#define ISA_STATUS 0x07 + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_DRQ 0x08 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_IDENTIFY 0xEC + +#define IDE_IDENT_SECTORS 20 +#define IDE_IDENT_MODEL 54 +#define IDE_IDENT_CAPABILITIES 98 +#define IDE_IDENT_CMDSETS 164 +#define IDE_IDENT_MAX_LBA 120 +#define IDE_IDENT_MAX_LBA_EXT 200 + +#define IO_BASE0 0x1F0 +#define IO_BASE1 0x170 +#define IO_CTRL0 0x3F4 +#define IO_CTRL1 0x374 + +#define MAX_IDE 4 +#define MAX_NSECS 128 +#define MAX_DISK_NSECS 0x10000000U +#define VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) + +static const struct { + unsigned short base; // I/O Base + unsigned short ctrl; // Control Base +} channels[2] = { + {IO_BASE0, IO_CTRL0}, + {IO_BASE1, IO_CTRL1}, +}; + +#define IO_BASE(ideno) (channels[(ideno) >> 1].base) +#define IO_CTRL(ideno) (channels[(ideno) >> 1].ctrl) + +static struct ide_device { + unsigned char valid; // 0 or 1 (If Device Really Exists) + unsigned int sets; // Commend Sets Supported + unsigned int size; // Size in Sectors + unsigned char model[41]; // Model in String +} ide_devices[MAX_IDE]; + +static int +ide_wait_ready(unsigned short iobase, bool check_error) { + int r; + while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY) + /* nothing */; + if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) { + return -1; + } + return 0; +} + +void +ide_init(void) { + static_assert((SECTSIZE % 4) == 0); + unsigned short ideno, iobase; + for (ideno = 0; ideno < MAX_IDE; ideno ++) { + /* assume that no device here */ + ide_devices[ideno].valid = 0; + + iobase = IO_BASE(ideno); + + /* wait device ready */ + ide_wait_ready(iobase, 0); + + /* step1: select drive */ + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4)); + ide_wait_ready(iobase, 0); + + /* step2: send ATA identify command */ + outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); + ide_wait_ready(iobase, 0); + + /* step3: polling */ + if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) { + continue ; + } + + /* device is ok */ + ide_devices[ideno].valid = 1; + + /* read identification space of the device */ + unsigned int buffer[128]; + insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); + + unsigned char *ident = (unsigned char *)buffer; + unsigned int sectors; + unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); + /* device use 48-bits or 28-bits addressing */ + if (cmdsets & (1 << 26)) { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); + } + else { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); + } + ide_devices[ideno].sets = cmdsets; + ide_devices[ideno].size = sectors; + + /* check if supports LBA */ + assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); + + unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; + unsigned int i, length = 40; + for (i = 0; i < length; i += 2) { + model[i] = data[i + 1], model[i + 1] = data[i]; + } + do { + model[i] = '\0'; + } while (i -- > 0 && model[i] == ' '); + + cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model); + } + + // enable ide interrupt + pic_enable(IRQ_IDE1); + pic_enable(IRQ_IDE2); +} + +bool +ide_device_valid(unsigned short ideno) { + return VALID_IDE(ideno); +} + +size_t +ide_device_size(unsigned short ideno) { + if (ide_device_valid(ideno)) { + return ide_devices[ideno].size; + } + return 0; +} + +int +ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_READ); + + int ret = 0; + for (; nsecs > 0; nsecs --, dst += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + insl(iobase, dst, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + +int +ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); + + int ret = 0; + for (; nsecs > 0; nsecs --, src += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + outsl(iobase, src, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + diff --git a/code/lab5/kern/driver/ide.h b/code/lab5/kern/driver/ide.h new file mode 100644 index 0000000..3e3fd21 --- /dev/null +++ b/code/lab5/kern/driver/ide.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_IDE_H__ +#define __KERN_DRIVER_IDE_H__ + +#include + +void ide_init(void); +bool ide_device_valid(unsigned short ideno); +size_t ide_device_size(unsigned short ideno); + +int ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs); +int ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs); + +#endif /* !__KERN_DRIVER_IDE_H__ */ + diff --git a/code/lab5/kern/driver/intr.c b/code/lab5/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab5/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab5/kern/driver/intr.h b/code/lab5/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab5/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab5/kern/driver/kbdreg.h b/code/lab5/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab5/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab5/kern/driver/picirq.c b/code/lab5/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab5/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab5/kern/driver/picirq.h b/code/lab5/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab5/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab5/kern/fs/fs.h b/code/lab5/kern/fs/fs.h new file mode 100644 index 0000000..92c05e7 --- /dev/null +++ b/code/lab5/kern/fs/fs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_FS_H__ +#define __KERN_FS_FS_H__ + +#include + +#define SECTSIZE 512 +#define PAGE_NSECT (PGSIZE / SECTSIZE) + +#define SWAP_DEV_NO 1 + +#endif /* !__KERN_FS_FS_H__ */ + diff --git a/code/lab5/kern/fs/swapfs.c b/code/lab5/kern/fs/swapfs.c new file mode 100644 index 0000000..d9f6090 --- /dev/null +++ b/code/lab5/kern/fs/swapfs.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include + +void +swapfs_init(void) { + static_assert((PGSIZE % SECTSIZE) == 0); + if (!ide_device_valid(SWAP_DEV_NO)) { + panic("swap fs isn't available.\n"); + } + max_swap_offset = ide_device_size(SWAP_DEV_NO) / (PGSIZE / SECTSIZE); +} + +int +swapfs_read(swap_entry_t entry, struct Page *page) { + return ide_read_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + +int +swapfs_write(swap_entry_t entry, struct Page *page) { + return ide_write_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + diff --git a/code/lab5/kern/fs/swapfs.h b/code/lab5/kern/fs/swapfs.h new file mode 100644 index 0000000..d433926 --- /dev/null +++ b/code/lab5/kern/fs/swapfs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_SWAPFS_H__ +#define __KERN_FS_SWAPFS_H__ + +#include +#include + +void swapfs_init(void); +int swapfs_read(swap_entry_t entry, struct Page *page); +int swapfs_write(swap_entry_t entry, struct Page *page); + +#endif /* !__KERN_FS_SWAPFS_H__ */ + diff --git a/code/lab5/kern/init/entry.S b/code/lab5/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab5/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab5/kern/init/init.c b/code/lab5/kern/init/init.c new file mode 100644 index 0000000..5546347 --- /dev/null +++ b/code/lab5/kern/init/init.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + vmm_init(); // init virtual memory management + proc_init(); // init process table + + ide_init(); // init ide devices + swap_init(); // init swap + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + cpu_idle(); // run idle process +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab5/kern/libs/rb_tree.c b/code/lab5/kern/libs/rb_tree.c new file mode 100644 index 0000000..0a5fcc8 --- /dev/null +++ b/code/lab5/kern/libs/rb_tree.c @@ -0,0 +1,528 @@ +#include +#include +#include +#include +#include +#include + +/* rb_node_create - create a new rb_node */ +static inline rb_node * +rb_node_create(void) { + return kmalloc(sizeof(rb_node)); +} + +/* rb_tree_empty - tests if tree is empty */ +static inline bool +rb_tree_empty(rb_tree *tree) { + rb_node *nil = tree->nil, *root = tree->root; + return root->left == nil; +} + +/* * + * rb_tree_create - creates a new red-black tree, the 'compare' function + * is required and returns 'NULL' if failed. + * + * Note that, root->left should always point to the node that is the root + * of the tree. And nil points to a 'NULL' node which should always be + * black and may have arbitrary children and parent node. + * */ +rb_tree * +rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)) { + assert(compare != NULL); + + rb_tree *tree; + rb_node *nil, *root; + + if ((tree = kmalloc(sizeof(rb_tree))) == NULL) { + goto bad_tree; + } + + tree->compare = compare; + + if ((nil = rb_node_create()) == NULL) { + goto bad_node_cleanup_tree; + } + + nil->parent = nil->left = nil->right = nil; + nil->red = 0; + tree->nil = nil; + + if ((root = rb_node_create()) == NULL) { + goto bad_node_cleanup_nil; + } + + root->parent = root->left = root->right = nil; + root->red = 0; + tree->root = root; + return tree; + +bad_node_cleanup_nil: + kfree(nil); +bad_node_cleanup_tree: + kfree(tree); +bad_tree: + return NULL; +} + +/* * + * FUNC_ROTATE - rotates as described in "Introduction to Algorithm". + * + * For example, FUNC_ROTATE(rb_left_rotate, left, right) can be expaned to a + * left-rotate function, which requires an red-black 'tree' and a node 'x' + * to be rotated on. Basically, this function, named rb_left_rotate, makes the + * parent of 'x' be the left child of 'x', 'x' the parent of its parent before + * rotation and finally fixes other nodes accordingly. + * + * FUNC_ROTATE(xx, left, right) means left-rotate, + * and FUNC_ROTATE(xx, right, left) means right-rotate. + * */ +#define FUNC_ROTATE(func_name, _left, _right) \ +static void \ +func_name(rb_tree *tree, rb_node *x) { \ + rb_node *nil = tree->nil, *y = x->_right; \ + assert(x != tree->root && x != nil && y != nil); \ + x->_right = y->_left; \ + if (y->_left != nil) { \ + y->_left->parent = x; \ + } \ + y->parent = x->parent; \ + if (x == x->parent->_left) { \ + x->parent->_left = y; \ + } \ + else { \ + x->parent->_right = y; \ + } \ + y->_left = x; \ + x->parent = y; \ + assert(!(nil->red)); \ +} + +FUNC_ROTATE(rb_left_rotate, left, right); +FUNC_ROTATE(rb_right_rotate, right, left); + +#undef FUNC_ROTATE + +#define COMPARE(tree, node1, node2) \ + ((tree))->compare((node1), (node2)) + +/* * + * rb_insert_binary - insert @node to red-black @tree as if it were + * a regular binary tree. This function is only intended to be called + * by function rb_insert. + * */ +static inline void +rb_insert_binary(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node, *nil = tree->nil, *root = tree->root; + + z->left = z->right = nil; + y = root, x = y->left; + while (x != nil) { + y = x; + x = (COMPARE(tree, x, node) > 0) ? x->left : x->right; + } + z->parent = y; + if (y == root || COMPARE(tree, y, z) > 0) { + y->left = z; + } + else { + y->right = z; + } +} + +/* rb_insert - insert a node to red-black tree */ +void +rb_insert(rb_tree *tree, rb_node *node) { + rb_insert_binary(tree, node); + node->red = 1; + + rb_node *x = node, *y; + +#define RB_INSERT_SUB(_left, _right) \ + do { \ + y = x->parent->parent->_right; \ + if (y->red) { \ + x->parent->red = 0; \ + y->red = 0; \ + x->parent->parent->red = 1; \ + x = x->parent->parent; \ + } \ + else { \ + if (x == x->parent->_right) { \ + x = x->parent; \ + rb_##_left##_rotate(tree, x); \ + } \ + x->parent->red = 0; \ + x->parent->parent->red = 1; \ + rb_##_right##_rotate(tree, x->parent->parent); \ + } \ + } while (0) + + while (x->parent->red) { + if (x->parent == x->parent->parent->left) { + RB_INSERT_SUB(left, right); + } + else { + RB_INSERT_SUB(right, left); + } + } + tree->root->left->red = 0; + assert(!(tree->nil->red) && !(tree->root->red)); + +#undef RB_INSERT_SUB +} + +/* * + * rb_tree_successor - returns the successor of @node, or nil + * if no successor exists. Make sure that @node must belong to @tree, + * and this function should only be called by rb_node_prev. + * */ +static inline rb_node * +rb_tree_successor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->right) != nil) { + while (y->left != nil) { + y = y->left; + } + return y; + } + else { + y = x->parent; + while (x == y->right) { + x = y, y = y->parent; + } + if (y == tree->root) { + return nil; + } + return y; + } +} + +/* * + * rb_tree_predecessor - returns the predecessor of @node, or nil + * if no predecessor exists, likes rb_tree_successor. + * */ +static inline rb_node * +rb_tree_predecessor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->left) != nil) { + while (y->right != nil) { + y = y->right; + } + return y; + } + else { + y = x->parent; + while (x == y->left) { + if (y == tree->root) { + return nil; + } + x = y, y = y->parent; + } + return y; + } +} + +/* * + * rb_search - returns a node with value 'equal' to @key (according to + * function @compare). If there're multiple nodes with value 'equal' to @key, + * the functions returns the one highest in the tree. + * */ +rb_node * +rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key) { + rb_node *nil = tree->nil, *node = tree->root->left; + int r; + while (node != nil && (r = compare(node, key)) != 0) { + node = (r > 0) ? node->left : node->right; + } + return (node != nil) ? node : NULL; +} + +/* * + * rb_delete_fixup - performs rotations and changes colors to restore + * red-black properties after a node is deleted. + * */ +static void +rb_delete_fixup(rb_tree *tree, rb_node *node) { + rb_node *x = node, *w, *root = tree->root->left; + +#define RB_DELETE_FIXUP_SUB(_left, _right) \ + do { \ + w = x->parent->_right; \ + if (w->red) { \ + w->red = 0; \ + x->parent->red = 1; \ + rb_##_left##_rotate(tree, x->parent); \ + w = x->parent->_right; \ + } \ + if (!w->_left->red && !w->_right->red) { \ + w->red = 1; \ + x = x->parent; \ + } \ + else { \ + if (!w->_right->red) { \ + w->_left->red = 0; \ + w->red = 1; \ + rb_##_right##_rotate(tree, w); \ + w = x->parent->_right; \ + } \ + w->red = x->parent->red; \ + x->parent->red = 0; \ + w->_right->red = 0; \ + rb_##_left##_rotate(tree, x->parent); \ + x = root; \ + } \ + } while (0) + + while (x != root && !x->red) { + if (x == x->parent->left) { + RB_DELETE_FIXUP_SUB(left, right); + } + else { + RB_DELETE_FIXUP_SUB(right, left); + } + } + x->red = 0; + +#undef RB_DELETE_FIXUP_SUB +} + +/* * + * rb_delete - deletes @node from @tree, and calls rb_delete_fixup to + * restore red-black properties. + * */ +void +rb_delete(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node; + rb_node *nil = tree->nil, *root = tree->root; + + y = (z->left == nil || z->right == nil) ? z : rb_tree_successor(tree, z); + x = (y->left != nil) ? y->left : y->right; + + assert(y != root && y != nil); + + x->parent = y->parent; + if (y == y->parent->left) { + y->parent->left = x; + } + else { + y->parent->right = x; + } + + bool need_fixup = !(y->red); + + if (y != z) { + if (z == z->parent->left) { + z->parent->left = y; + } + else { + z->parent->right = y; + } + z->left->parent = z->right->parent = y; + *y = *z; + } + if (need_fixup) { + rb_delete_fixup(tree, x); + } +} + +/* rb_tree_destroy - destroy a tree and free memory */ +void +rb_tree_destroy(rb_tree *tree) { + kfree(tree->root); + kfree(tree->nil); + kfree(tree); +} + +/* * + * rb_node_prev - returns the predecessor node of @node in @tree, + * or 'NULL' if no predecessor exists. + * */ +rb_node * +rb_node_prev(rb_tree *tree, rb_node *node) { + rb_node *prev = rb_tree_predecessor(tree, node); + return (prev != tree->nil) ? prev : NULL; +} + +/* * + * rb_node_next - returns the successor node of @node in @tree, + * or 'NULL' if no successor exists. + * */ +rb_node * +rb_node_next(rb_tree *tree, rb_node *node) { + rb_node *next = rb_tree_successor(tree, node); + return (next != tree->nil) ? next : NULL; +} + +/* rb_node_root - returns the root node of a @tree, or 'NULL' if tree is empty */ +rb_node * +rb_node_root(rb_tree *tree) { + rb_node *node = tree->root->left; + return (node != tree->nil) ? node : NULL; +} + +/* rb_node_left - gets the left child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_left(rb_tree *tree, rb_node *node) { + rb_node *left = node->left; + return (left != tree->nil) ? left : NULL; +} + +/* rb_node_right - gets the right child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_right(rb_tree *tree, rb_node *node) { + rb_node *right = node->right; + return (right != tree->nil) ? right : NULL; +} + +int +check_tree(rb_tree *tree, rb_node *node) { + rb_node *nil = tree->nil; + if (node == nil) { + assert(!node->red); + return 1; + } + if (node->left != nil) { + assert(COMPARE(tree, node, node->left) >= 0); + assert(node->left->parent == node); + } + if (node->right != nil) { + assert(COMPARE(tree, node, node->right) <= 0); + assert(node->right->parent == node); + } + if (node->red) { + assert(!node->left->red && !node->right->red); + } + int hb_left = check_tree(tree, node->left); + int hb_right = check_tree(tree, node->right); + assert(hb_left == hb_right); + int hb = hb_left; + if (!node->red) { + hb ++; + } + return hb; +} + +static void * +check_safe_kmalloc(size_t size) { + void *ret = kmalloc(size); + assert(ret != NULL); + return ret; +} + +struct check_data { + long data; + rb_node rb_link; +}; + +#define rbn2data(node) \ + (to_struct(node, struct check_data, rb_link)) + +static inline int +check_compare1(rb_node *node1, rb_node *node2) { + return rbn2data(node1)->data - rbn2data(node2)->data; +} + +static inline int +check_compare2(rb_node *node, void *key) { + return rbn2data(node)->data - (long)key; +} + +void +check_rb_tree(void) { + rb_tree *tree = rb_tree_create(check_compare1); + assert(tree != NULL); + + rb_node *nil = tree->nil, *root = tree->root; + assert(!nil->red && root->left == nil); + + int total = 1000; + struct check_data **all = check_safe_kmalloc(sizeof(struct check_data *) * total); + + long i; + for (i = 0; i < total; i ++) { + all[i] = check_safe_kmalloc(sizeof(struct check_data)); + all[i]->data = i; + } + + int *mark = check_safe_kmalloc(sizeof(int) * total); + memset(mark, 0, sizeof(int) * total); + + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + int j = (rand() % (total - i)) + i; + struct check_data *z = all[i]; + all[i] = all[j]; + all[j] = z; + } + + memset(mark, 0, sizeof(int) * total); + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_node *node; + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)(all[i]->data)); + assert(node != NULL && node == &(all[i]->rb_link)); + } + + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)i); + assert(node != NULL && rbn2data(node)->data == i); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(!nil->red && root->left == nil); + + long max = 32; + if (max > total) { + max = total; + } + + for (i = 0; i < max; i ++) { + all[i]->data = max; + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + for (i = 0; i < max; i ++) { + node = rb_search(tree, check_compare2, (void *)max); + assert(node != NULL && rbn2data(node)->data == max); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(rb_tree_empty(tree)); + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_tree_destroy(tree); + + for (i = 0; i < total; i ++) { + kfree(all[i]); + } + + kfree(mark); + kfree(all); +} + diff --git a/code/lab5/kern/libs/rb_tree.h b/code/lab5/kern/libs/rb_tree.h new file mode 100644 index 0000000..a2aa9aa --- /dev/null +++ b/code/lab5/kern/libs/rb_tree.h @@ -0,0 +1,32 @@ +#ifndef __KERN_LIBS_RB_TREE_H__ +#define __KERN_LIBS_RB_TREE_H__ + +#include + +typedef struct rb_node { + bool red; // if red = 0, it's a black node + struct rb_node *parent; + struct rb_node *left, *right; +} rb_node; + +typedef struct rb_tree { + // compare function should return -1 if *node1 < *node2, 1 if *node1 > *node2, and 0 otherwise + int (*compare)(rb_node *node1, rb_node *node2); + struct rb_node *nil, *root; +} rb_tree; + +rb_tree *rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)); +void rb_tree_destroy(rb_tree *tree); +void rb_insert(rb_tree *tree, rb_node *node); +void rb_delete(rb_tree *tree, rb_node *node); +rb_node *rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key); +rb_node *rb_node_prev(rb_tree *tree, rb_node *node); +rb_node *rb_node_next(rb_tree *tree, rb_node *node); +rb_node *rb_node_root(rb_tree *tree); +rb_node *rb_node_left(rb_tree *tree, rb_node *node); +rb_node *rb_node_right(rb_tree *tree, rb_node *node); + +void check_rb_tree(void); + +#endif /* !__KERN_LIBS_RBTREE_H__ */ + diff --git a/code/lab5/kern/libs/readline.c b/code/lab5/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab5/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab5/kern/libs/stdio.c b/code/lab5/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab5/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab5/kern/mm/default_pmm.c b/code/lab5/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab5/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab5/kern/mm/default_pmm.h b/code/lab5/kern/mm/default_pmm.h new file mode 100644 index 0000000..5f4e6fc --- /dev/null +++ b/code/lab5/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; +extern free_area_t free_area; +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab5/kern/mm/kmalloc.c b/code/lab5/kern/mm/kmalloc.c new file mode 100644 index 0000000..aa5bb90 --- /dev/null +++ b/code/lab5/kern/mm/kmalloc.c @@ -0,0 +1,640 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The slab allocator used in ucore is based on an algorithm first introduced by + Jeff Bonwick for the SunOS operating system. The paper can be download from + http://citeseer.ist.psu.edu/bonwick94slab.html + An implementation of the Slab Allocator as described in outline in; + UNIX Internals: The New Frontiers by Uresh Vahalia + Pub: Prentice Hall ISBN 0-13-101908-2 + Within a kernel, a considerable amount of memory is allocated for a finite set + of objects such as file descriptors and other common structures. Jeff found that + the amount of time required to initialize a regular object in the kernel exceeded + the amount of time required to allocate and deallocate it. His conclusion was + that instead of freeing the memory back to a global pool, he would have the memory + remain initialized for its intended purpose. + In our simple slab implementation, the the high-level organization of the slab + structures is simplied. At the highest level is an array slab_cache[SLAB_CACHE_NUM], + and each array element is a slab_cache which has slab chains. Each slab_cache has + two list, one list chains the full allocated slab, and another list chains the notfull + allocated(maybe empty) slab. And each slab has fixed number(2^n) of pages. In each + slab, there are a lot of objects (such as ) with same fixed size(32B ~ 128KB). + + +----------------------------------+ + | slab_cache[0] for 0~32B obj | + +----------------------------------+ + | slab_cache[1] for 33B~64B obj |-->lists for slabs + +----------------------------------+ | + | slab_cache[2] for 65B~128B obj | | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + +----------------------------------+ | + | slab_cache[12]for 64KB~128KB obj | | + +----------------------------------+ | + | + slabs_full/slabs_not +---------------------+ + -<-----------<----------<-+ + | | | + slab1 slab2 slab3... + | + |-------|-------| + pages1 pages2 pages3... + | + | + | + slab_t+n*bufctl_t+obj1-obj2-obj3...objn (the size of obj is small) + | + OR + | + obj1-obj2-obj3...objn WITH slab_t+n*bufctl_t in another slab (the size of obj is BIG) + + The important functions are: + kmem_cache_grow(kmem_cache_t *cachep) + kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) + kmalloc(size_t size): used by outside functions need dynamicly get memory + kfree(void *objp): used by outside functions need dynamicly release memory +*/ + +#define BUFCTL_END 0xFFFFFFFFL // the signature of the last bufctl +#define SLAB_LIMIT 0xFFFFFFFEL // the max value of obj number + +typedef size_t kmem_bufctl_t; //the index of obj in slab + +typedef struct slab_s { + list_entry_t slab_link; // the list entry linked to kmem_cache list + void *s_mem; // the kernel virtual address of the first obj in slab + size_t inuse; // the number of allocated objs + size_t offset; // the first obj's offset value in slab + kmem_bufctl_t free; // the first free obj's index in slab +} slab_t; + +// get the slab address according to the link element (see list.h) +#define le2slab(le, member) \ + to_struct((le), slab_t, member) + +typedef struct kmem_cache_s kmem_cache_t; + + +struct kmem_cache_s { + list_entry_t slabs_full; // list for fully allocated slabs + list_entry_t slabs_notfull; // list for not-fully allocated slabs + + size_t objsize; // the fixed size of obj + size_t num; // number of objs per slab + size_t offset; // this first obj's offset in slab + bool off_slab; // the control part of slab in slab or not. + + /* order of pages per slab (2^n) */ + size_t page_order; + + kmem_cache_t *slab_cachep; +}; + +#define MIN_SIZE_ORDER 5 // 32 +#define MAX_SIZE_ORDER 17 // 128k +#define SLAB_CACHE_NUM (MAX_SIZE_ORDER - MIN_SIZE_ORDER + 1) + +static kmem_cache_t slab_cache[SLAB_CACHE_NUM]; + +static void init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align); +static void check_slab(void); + + +//slab_init - call init_kmem_cache function to reset the slab_cache array +static void +slab_init(void) { + size_t i; + //the align bit for obj in slab. 2^n could be better for performance + size_t align = 16; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + init_kmem_cache(slab_cache + i, 1 << (i + MIN_SIZE_ORDER), align); + } + check_slab(); +} + +inline void +kmalloc_init(void) { + slab_init(); + cprintf("kmalloc_init() succeeded!\n"); +} + +//slab_allocated - summary the total size of allocated objs +static size_t +slab_allocated(void) { + size_t total = 0; + int i; + bool intr_flag; + local_intr_save(intr_flag); + { + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + list_entry_t *list, *le; + list = le = &(cachep->slabs_full); + while ((le = list_next(le)) != list) { + total += cachep->num * cachep->objsize; + } + list = le = &(cachep->slabs_notfull); + while ((le = list_next(le)) != list) { + slab_t *slabp = le2slab(le, slab_link); + total += slabp->inuse * cachep->objsize; + } + } + } + local_intr_restore(intr_flag); + return total; +} + +inline size_t +kallocated(void) { + return slab_allocated(); +} + +// slab_mgmt_size - get the size of slab control area (slab_t+num*kmem_bufctl_t) +static size_t +slab_mgmt_size(size_t num, size_t align) { + return ROUNDUP(sizeof(slab_t) + num * sizeof(kmem_bufctl_t), align); +} + +// cacahe_estimate - estimate the number of objs in a slab +static void +cache_estimate(size_t order, size_t objsize, size_t align, bool off_slab, size_t *remainder, size_t *num) { + size_t nr_objs, mgmt_size; + size_t slab_size = (PGSIZE << order); + + if (off_slab) { + mgmt_size = 0; + nr_objs = slab_size / objsize; + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + } + else { + nr_objs = (slab_size - sizeof(slab_t)) / (objsize + sizeof(kmem_bufctl_t)); + while (slab_mgmt_size(nr_objs, align) + nr_objs * objsize > slab_size) { + nr_objs --; + } + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + mgmt_size = slab_mgmt_size(nr_objs, align); + } + *num = nr_objs; + *remainder = slab_size - nr_objs * objsize - mgmt_size; +} + +// calculate_slab_order - estimate the size(4K~4M) of slab +// paramemters: +// cachep: the slab_cache +// objsize: the size of obj +// align: align bit for objs +// off_slab: the control part of slab in slab or not +// left_over: the size of can not be used area in slab +static void +calculate_slab_order(kmem_cache_t *cachep, size_t objsize, size_t align, bool off_slab, size_t *left_over) { + size_t order; + for (order = 0; order <= KMALLOC_MAX_ORDER; order ++) { + size_t num, remainder; + cache_estimate(order, objsize, align, off_slab, &remainder, &num); + if (num != 0) { + if (off_slab) { + size_t off_slab_limit = objsize - sizeof(slab_t); + off_slab_limit /= sizeof(kmem_bufctl_t); + if (num > off_slab_limit) { + panic("off_slab: objsize = %d, num = %d.", objsize, num); + } + } + if (remainder * 8 <= (PGSIZE << order)) { + cachep->num = num; + cachep->page_order = order; + if (left_over != NULL) { + *left_over = remainder; + } + return ; + } + } + } + panic("calculate_slab_over: failed."); +} + +// getorder - find order, should satisfy n <= minest 2^order +static inline size_t +getorder(size_t n) { + size_t order = MIN_SIZE_ORDER, order_size = (1 << order); + for (; order <= MAX_SIZE_ORDER; order ++, order_size <<= 1) { + if (n <= order_size) { + return order; + } + } + panic("getorder failed. %d\n", n); +} + +// init_kmem_cache - initial a slab_cache cachep according to the obj with the size = objsize +static void +init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align) { + list_init(&(cachep->slabs_full)); + list_init(&(cachep->slabs_notfull)); + + objsize = ROUNDUP(objsize, align); + cachep->objsize = objsize; + cachep->off_slab = (objsize >= (PGSIZE >> 3)); + + size_t left_over; + calculate_slab_order(cachep, objsize, align, cachep->off_slab, &left_over); + + assert(cachep->num > 0); + + size_t mgmt_size = slab_mgmt_size(cachep->num, align); + + if (cachep->off_slab && left_over >= mgmt_size) { + cachep->off_slab = 0; + } + + if (cachep->off_slab) { + cachep->offset = 0; + cachep->slab_cachep = slab_cache + (getorder(mgmt_size) - MIN_SIZE_ORDER); + } + else { + cachep->offset = mgmt_size; + } +} + +static void *kmem_cache_alloc(kmem_cache_t *cachep); + +#define slab_bufctl(slabp) \ + ((kmem_bufctl_t*)(((slab_t *)(slabp)) + 1)) + +// kmem_cache_slabmgmt - get the address of a slab according to page +// - and initialize the slab according to cachep +static slab_t * +kmem_cache_slabmgmt(kmem_cache_t *cachep, struct Page *page) { + void *objp = page2kva(page); + slab_t *slabp; + if (cachep->off_slab) { + if ((slabp = kmem_cache_alloc(cachep->slab_cachep)) == NULL) { + return NULL; + } + } + else { + slabp = page2kva(page); + } + slabp->inuse = 0; + slabp->offset = cachep->offset; + slabp->s_mem = objp + cachep->offset; + return slabp; +} + +#define SET_PAGE_CACHE(page, cachep) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + kmem_cache_t **__cachepp = (kmem_cache_t **)&(__page->page_link.next); \ + *__cachepp = (kmem_cache_t *)(cachep); \ + } while (0) + +#define SET_PAGE_SLAB(page, slabp) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + slab_t **__cachepp = (slab_t **)&(__page->page_link.prev); \ + *__cachepp = (slab_t *)(slabp); \ + } while (0) + +// kmem_cache_grow - allocate a new slab by calling alloc_pages +// - set control area in the new slab +static bool +kmem_cache_grow(kmem_cache_t *cachep) { + struct Page *page = alloc_pages(1 << cachep->page_order); + if (page == NULL) { + goto failed; + } + + slab_t *slabp; + if ((slabp = kmem_cache_slabmgmt(cachep, page)) == NULL) { + goto oops; + } + + size_t order_size = (1 << cachep->page_order); + do { + //setup this page in the free list (see memlayout.h: struct page)??? + SET_PAGE_CACHE(page, cachep); + SET_PAGE_SLAB(page, slabp); + //this page is used for slab + SetPageSlab(page); + page ++; + } while (-- order_size); + + int i; + for (i = 0; i < cachep->num; i ++) { + slab_bufctl(slabp)[i] = i + 1; + } + slab_bufctl(slabp)[cachep->num - 1] = BUFCTL_END; + slabp->free = 0; + + bool intr_flag; + local_intr_save(intr_flag); + { + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } + local_intr_restore(intr_flag); + return 1; + +oops: + free_pages(page, 1 << cachep->page_order); +failed: + return 0; +} + +// kmem_cache_alloc_one - allocate a obj in a slab +static void * +kmem_cache_alloc_one(kmem_cache_t *cachep, slab_t *slabp) { + slabp->inuse ++; + void *objp = slabp->s_mem + slabp->free * cachep->objsize; + slabp->free = slab_bufctl(slabp)[slabp->free]; + + if (slabp->free == BUFCTL_END) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_full), &(slabp->slab_link)); + } + return objp; +} + +// kmem_cache_alloc - call kmem_cache_alloc_one function to allocate a obj +// - if no free obj, try to allocate a slab +static void * +kmem_cache_alloc(kmem_cache_t *cachep) { + void *objp; + bool intr_flag; + +try_again: + local_intr_save(intr_flag); + if (list_empty(&(cachep->slabs_notfull))) { + goto alloc_new_slab; + } + slab_t *slabp = le2slab(list_next(&(cachep->slabs_notfull)), slab_link); + objp = kmem_cache_alloc_one(cachep, slabp); + local_intr_restore(intr_flag); + return objp; + +alloc_new_slab: + local_intr_restore(intr_flag); + + if (kmem_cache_grow(cachep)) { + goto try_again; + } + return NULL; +} + +// kmalloc - simple interface used by outside functions +// - to allocate a free memory using kmem_cache_alloc function +void * +kmalloc(size_t size) { + assert(size > 0); + size_t order = getorder(size); + if (order > MAX_SIZE_ORDER) { + return NULL; + } + return kmem_cache_alloc(slab_cache + (order - MIN_SIZE_ORDER)); +} + +static void kmem_cache_free(kmem_cache_t *cachep, void *obj); + +// kmem_slab_destroy - call free_pages & kmem_cache_free to free a slab +static void +kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) { + struct Page *page = kva2page(slabp->s_mem - slabp->offset); + + struct Page *p = page; + size_t order_size = (1 << cachep->page_order); + do { + assert(PageSlab(p)); + ClearPageSlab(p); + p ++; + } while (-- order_size); + + free_pages(page, 1 << cachep->page_order); + + if (cachep->off_slab) { + kmem_cache_free(cachep->slab_cachep, slabp); + } +} + +// kmem_cache_free_one - free an obj in a slab +// - if slab->inuse==0, then free the slab +static void +kmem_cache_free_one(kmem_cache_t *cachep, slab_t *slabp, void *objp) { + //should not use divide operator ??? + size_t objnr = (objp - slabp->s_mem) / cachep->objsize; + slab_bufctl(slabp)[objnr] = slabp->free; + slabp->free = objnr; + + slabp->inuse --; + + if (slabp->inuse == 0) { + list_del(&(slabp->slab_link)); + kmem_slab_destroy(cachep, slabp); + } + else if (slabp->inuse == cachep->num -1 ) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } +} + +#define GET_PAGE_CACHE(page) \ + (kmem_cache_t *)((page)->page_link.next) + +#define GET_PAGE_SLAB(page) \ + (slab_t *)((page)->page_link.prev) + +// kmem_cache_free - call kmem_cache_free_one function to free an obj +static void +kmem_cache_free(kmem_cache_t *cachep, void *objp) { + bool intr_flag; + struct Page *page = kva2page(objp); + + if (!PageSlab(page)) { + panic("not a slab page %08x\n", objp); + } + local_intr_save(intr_flag); + { + kmem_cache_free_one(cachep, GET_PAGE_SLAB(page), objp); + } + local_intr_restore(intr_flag); +} + +// kfree - simple interface used by ooutside functions to free an obj +void +kfree(void *objp) { + kmem_cache_free(GET_PAGE_CACHE(kva2page(objp)), objp); +} + +static inline void +check_slab_empty(void) { + int i; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + assert(list_empty(&(cachep->slabs_full))); + assert(list_empty(&(cachep->slabs_notfull))); + } +} + +void +check_slab(void) { + int i; + void *v0, *v1; + + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = slab_allocated(); + + /* slab must be empty now */ + check_slab_empty(); + assert(slab_allocated() == 0); + + kmem_cache_t *cachep0, *cachep1; + + cachep0 = slab_cache; + assert(cachep0->objsize == 32 && cachep0->num > 1 && !cachep0->off_slab); + assert((v0 = kmalloc(16)) != NULL); + + slab_t *slabp0, *slabp1; + + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + assert(slabp0->inuse == 1 && list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + struct Page *p0, *p1; + size_t order_size; + + + p0 = kva2page(slabp0->s_mem - slabp0->offset), p1 = p0; + order_size = (1 << cachep0->page_order); + for (i = 0; i < cachep0->page_order; i ++, p1 ++) { + assert(PageSlab(p1)); + assert(GET_PAGE_CACHE(p1) == cachep0 && GET_PAGE_SLAB(p1) == slabp0); + } + + assert(v0 == slabp0->s_mem); + assert((v1 = kmalloc(16)) != NULL && v1 == v0 + 32); + + kfree(v0); + assert(slabp0->free == 0); + kfree(v1); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->page_order; i ++, p0 ++) { + assert(!PageSlab(p0)); + } + + + v0 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + for (i = 0; i < cachep0->num - 1; i ++) { + kmalloc(16); + } + + assert(slabp0->inuse == cachep0->num); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + v1 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + kfree(v0); + assert(list_empty(&(cachep0->slabs_full))); + assert(list_next(&(slabp0->slab_link)) == &(slabp1->slab_link) + || list_next(&(slabp1->slab_link)) == &(slabp0->slab_link)); + + kfree(v1); + assert(!list_empty(&(cachep0->slabs_notfull))); + assert(list_next(&(cachep0->slabs_notfull)) == &(slabp0->slab_link)); + assert(list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + v1 = kmalloc(16); + assert(v1 == v0); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->num; i ++) { + kfree(v1 + i * cachep0->objsize); + } + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + cachep0 = slab_cache; + + bool has_off_slab = 0; + for (i = 0; i < SLAB_CACHE_NUM; i ++, cachep0 ++) { + if (cachep0->off_slab) { + has_off_slab = 1; + cachep1 = cachep0->slab_cachep; + if (!cachep1->off_slab) { + break; + } + } + } + + if (!has_off_slab) { + goto check_pass; + } + + assert(cachep0->off_slab && !cachep1->off_slab); + assert(cachep1 < cachep0); + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + assert(list_empty(&(cachep1->slabs_full))); + assert(list_empty(&(cachep1->slabs_notfull))); + + v0 = kmalloc(cachep0->objsize); + p0 = kva2page(v0); + assert(page2kva(p0) == v0); + + if (cachep0->num == 1) { + assert(!list_empty(&(cachep0->slabs_full))); + slabp0 = le2slab(list_next(&(cachep0->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + } + + assert(slabp0 != NULL); + + if (cachep1->num == 1) { + assert(!list_empty(&(cachep1->slabs_full))); + slabp1 = le2slab(list_next(&(cachep1->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep1->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep1->slabs_notfull)), slab_link); + } + + assert(slabp1 != NULL); + + order_size = (1 << cachep0->page_order); + for (i = 0; i < order_size; i ++, p0 ++) { + assert(PageSlab(p0)); + assert(GET_PAGE_CACHE(p0) == cachep0 && GET_PAGE_SLAB(p0) == slabp0); + } + + kfree(v0); + +check_pass: + + check_rb_tree(); + check_slab_empty(); + assert(slab_allocated() == 0); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == slab_allocated()); + + cprintf("check_slab() succeeded!\n"); +} + diff --git a/code/lab5/kern/mm/kmalloc.h b/code/lab5/kern/mm/kmalloc.h new file mode 100644 index 0000000..8617975 --- /dev/null +++ b/code/lab5/kern/mm/kmalloc.h @@ -0,0 +1,16 @@ +#ifndef __KERN_MM_SLAB_H__ +#define __KERN_MM_SLAB_H__ + +#include + +#define KMALLOC_MAX_ORDER 10 + +void kmalloc_init(void); + +void *kmalloc(size_t n); +void kfree(void *objp); + +size_t kallocated(void); + +#endif /* !__KERN_MM_SLAB_H__ */ + diff --git a/code/lab5/kern/mm/memlayout.h b/code/lab5/kern/mm/memlayout.h new file mode 100644 index 0000000..d84bf93 --- /dev/null +++ b/code/lab5/kern/mm/memlayout.h @@ -0,0 +1,169 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | Invalid Memory (*) | --/-- + * USERTOP -------------> +---------------------------------+ 0xB0000000 + * | User stack | + * +---------------------------------+ + * | | + * : : + * | ~~~~~~~~~~~~~~~~ | + * : : + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | User Program & Heap | + * UTEXT ---------------> +---------------------------------+ 0x00800000 + * | Invalid Memory (*) | --/-- + * | - - - - - - - - - - - - - - - | + * | User STAB Data (optional) | + * USERBASE, USTAB------> +---------------------------------+ 0x00200000 + * | Invalid Memory (*) | --/-- + * 0 -------------------> +---------------------------------+ 0x00000000 + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#define USERTOP 0xB0000000 +#define USTACKTOP USERTOP +#define USTACKPAGE 256 // # of pages in user stack +#define USTACKSIZE (USTACKPAGE * PGSIZE) // sizeof user stack + +#define USERBASE 0x00200000 +#define UTEXT 0x00800000 // where user programs generally begin +#define USTAB USERBASE // the location of the user STABS data structure + +#define USER_ACCESS(start, end) \ +(USERBASE <= (start) && (start) < (end) && (end) <= USERTOP) + +#define KERN_ACCESS(start, end) \ +(KERNBASE <= (start) && (start) < (end) && (end) <= KERNTOP) + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; +typedef pte_t swap_entry_t; //the pte can also be a swap entry + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // used in buddy system, stores the order (the X in 2^X) of the continuous memory block + int zone_num; // used in buddy system, the No. of zone which the page belongs to + list_entry_t page_link; // free list link + list_entry_t pra_page_link; // used for pra (page replace algorithm) + uintptr_t pra_vaddr; // used for pra (page replace algorithm) +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +/* for slab style kmalloc */ +#define PG_slab 2 // page frame is included in a slab +#define SetPageSlab(page) set_bit(PG_slab, &((page)->flags)) +#define ClearPageSlab(page) clear_bit(PG_slab, &((page)->flags)) +#define PageSlab(page) test_bit(PG_slab, &((page)->flags)) + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab5/kern/mm/mmu.h b/code/lab5/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab5/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab5/kern/mm/pmm.c b/code/lab5/kern/mm/pmm.c new file mode 100644 index 0000000..cc3f28c --- /dev/null +++ b/code/lab5/kern/mm/pmm.c @@ -0,0 +1,759 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + + while (1) + { + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + + if (page != NULL || n > 1 || swap_init_ok == 0) break; + + extern struct mm_struct *check_mm_struct; + //cprintf("page %x, call swap_out in alloc_pages %d\n",page, n); + swap_out(check_mm_struct, n, 0); + } + //cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages)); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + + kmalloc_init(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +void +unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + do { + pte_t *ptep = get_pte(pgdir, start, 0); + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + if (*ptep != 0) { + page_remove_pte(pgdir, start, ptep); + } + start += PGSIZE; + } while (start != 0 && start < end); +} + +void +exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + start = ROUNDDOWN(start, PTSIZE); + do { + int pde_idx = PDX(start); + if (pgdir[pde_idx] & PTE_P) { + free_page(pde2page(pgdir[pde_idx])); + pgdir[pde_idx] = 0; + } + start += PTSIZE; + } while (start != 0 && start < end); +} +/* copy_range - copy content of memory (start, end) of one process A to another process B + * @to: the addr of process B's Page Directory + * @from: the addr of process A's Page Directory + * @share: flags to indicate to dup OR share. We just use dup method, so it didn't be used. + * + * CALL GRAPH: copy_mm-->dup_mmap-->copy_range + */ +int +copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + // copy content by page unit. + do { + //call get_pte to find process A's pte according to the addr start + pte_t *ptep = get_pte(from, start, 0), *nptep; + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + //call get_pte to find process B's pte according to the addr start. If pte is NULL, just alloc a PT + if (*ptep & PTE_P) { + if ((nptep = get_pte(to, start, 1)) == NULL) { + return -E_NO_MEM; + } + uint32_t perm = (*ptep & PTE_USER); + //get page from ptep + struct Page *page = pte2page(*ptep); + // alloc a page for process B + struct Page *npage=alloc_page(); + assert(page!=NULL); + assert(npage!=NULL); + int ret=0; + /* LAB5:EXERCISE2 YOUR CODE + * replicate content of page to npage, build the map of phy addr of nage with the linear addr start + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h) + * page_insert: build the map of phy addr of an Page with the linear addr la + * memcpy: typical memory copy function + * + * (1) find src_kvaddr: the kernel virtual address of page + * (2) find dst_kvaddr: the kernel virtual address of npage + * (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE + * (4) build the map of phy addr of nage with the linear addr start + */ + assert(ret == 0); + } + start += PGSIZE; + } while (start != 0 && start < end); + return 0; +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +// pgdir_alloc_page - call alloc_page & page_insert functions to +// - allocate a page size memory & setup an addr map +// - pa<->la with linear address la and the PDT pgdir +struct Page * +pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { + struct Page *page = alloc_page(); + if (page != NULL) { + if (page_insert(pgdir, page, la, perm) != 0) { + free_page(page); + return NULL; + } + if (swap_init_ok){ + if(check_mm_struct!=NULL) { + swap_map_swappable(check_mm_struct, la, page, 0); + page->pra_vaddr=la; + assert(page_ref(page) == 1); + //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); + } + else { //now current is existed, should fix it in the future + //swap_map_swappable(current->mm, la, page, 0); + //page->pra_vaddr=la; + //assert(page_ref(page) == 1); + //panic("pgdir_alloc_page: no pages. now current is existed, should fix it in the future\n"); + } + } + + } + + return page; +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} diff --git a/code/lab5/kern/mm/pmm.h b/code/lab5/kern/mm/pmm.h new file mode 100644 index 0000000..1e229a7 --- /dev/null +++ b/code/lab5/kern/mm/pmm.h @@ -0,0 +1,145 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); +struct Page *pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm); +void unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +void exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab5/kern/mm/swap.c b/code/lab5/kern/mm/swap.c new file mode 100644 index 0000000..281889d --- /dev/null +++ b/code/lab5/kern/mm/swap.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1 +#define CHECK_VALID_VIR_PAGE_NUM 5 +#define BEING_CHECK_VALID_VADDR 0X1000 +#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000 +// the max number of valid physical page for check +#define CHECK_VALID_PHY_PAGE_NUM 4 +// the max access seq number +#define MAX_SEQ_NO 10 + +static struct swap_manager *sm; +size_t max_swap_offset; + +volatile int swap_init_ok = 0; + +unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM]; + +unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO]; + +static void check_swap(void); + +int +swap_init(void) +{ + swapfs_init(); + + if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) + { + panic("bad max_swap_offset %08x.\n", max_swap_offset); + } + + + sm = &swap_manager_fifo; + int r = sm->init(); + + if (r == 0) + { + swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + check_swap(); + } + + return r; +} + +int +swap_init_mm(struct mm_struct *mm) +{ + return sm->init_mm(mm); +} + +int +swap_tick_event(struct mm_struct *mm) +{ + return sm->tick_event(mm); +} + +int +swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + return sm->map_swappable(mm, addr, page, swap_in); +} + +int +swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return sm->set_unswappable(mm, addr); +} + +volatile unsigned int swap_out_num=0; + +int +swap_out(struct mm_struct *mm, int n, int in_tick) +{ + int i; + for (i = 0; i != n; ++ i) + { + uintptr_t v; + //struct Page **ptr_page=NULL; + struct Page *page; + // cprintf("i %d, SWAP: call swap_out_victim\n",i); + int r = sm->swap_out_victim(mm, &page, in_tick); + if (r != 0) { + cprintf("i %d, swap_out: call swap_out_victim failed\n",i); + break; + } + //assert(!PageReserved(page)); + + //cprintf("SWAP: choose victim page 0x%08x\n", page); + + v=page->pra_vaddr; + pte_t *ptep = get_pte(mm->pgdir, v, 0); + assert((*ptep & PTE_P) != 0); + + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { + cprintf("SWAP: failed to save\n"); + sm->map_swappable(mm, v, page, 0); + continue; + } + else { + cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; + free_page(page); + } + + tlb_invalidate(mm->pgdir, v); + } + return i; +} + +int +swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) +{ + struct Page *result = alloc_page(); + assert(result!=NULL); + + pte_t *ptep = get_pte(mm->pgdir, addr, 0); + // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); + + int r; + if ((r = swapfs_read((*ptep), result)) != 0) + { + assert(r!=0); + } + cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr); + *ptr_result=result; + return 0; +} + + + +static inline void +check_content_set(void) +{ + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x1010 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x2010 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x3010 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + *(unsigned char *)0x4010 = 0x0d; + assert(pgfault_num==4); +} + +static inline int +check_content_access(void) +{ + int ret = sm->check_swap(); + return ret; +} + +struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM]; +pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM]; +unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM]; + +extern free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +check_swap(void) +{ + //backup mem env + int ret, count = 0, total = 0, i; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + //assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + + //now we set the phy pages env + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + extern struct mm_struct *check_mm_struct; + assert(check_mm_struct == NULL); + + check_mm_struct = mm; + + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + //setup the temp Page Table vaddr 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + pte_t *temp_ptep=NULL; + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); + assert(temp_ptep!= NULL); + cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + + for (i=0;iphy_page environment for page relpacement algorithm + + + pgfault_num=0; + + check_content_set(); + assert( nr_free == 0); + for(i = 0; ipgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + nr_free = nr_free_store; + free_list = free_list_store; + + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + cprintf("count is %d, total is %d\n",count,total); + //assert(count == 0); + + cprintf("check_swap() succeeded!\n"); +} diff --git a/code/lab5/kern/mm/swap.h b/code/lab5/kern/mm/swap.h new file mode 100644 index 0000000..5d4aea8 --- /dev/null +++ b/code/lab5/kern/mm/swap.h @@ -0,0 +1,65 @@ +#ifndef __KERN_MM_SWAP_H__ +#define __KERN_MM_SWAP_H__ + +#include +#include +#include +#include + +/* * + * swap_entry_t + * -------------------------------------------- + * | offset | reserved | 0 | + * -------------------------------------------- + * 24 bits 7 bits 1 bit + * */ + +#define MAX_SWAP_OFFSET_LIMIT (1 << 24) + +extern size_t max_swap_offset; + +/* * + * swap_offset - takes a swap_entry (saved in pte), and returns + * the corresponding offset in swap mem_map. + * */ +#define swap_offset(entry) ({ \ + size_t __offset = (entry >> 8); \ + if (!(__offset > 0 && __offset < max_swap_offset)) { \ + panic("invalid swap_entry_t = %08x.\n", entry); \ + } \ + __offset; \ + }) + +struct swap_manager +{ + const char *name; + /* Global initialization for the swap manager */ + int (*init) (void); + /* Initialize the priv data inside mm_struct */ + int (*init_mm) (struct mm_struct *mm); + /* Called when tick interrupt occured */ + int (*tick_event) (struct mm_struct *mm); + /* Called when map a swappable page into the mm_struct */ + int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); + /* When a page is marked as shared, this routine is called to + * delete the addr entry from the swap manager */ + int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); + /* Try to swap out a page, return then victim */ + int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); + /* check the page relpacement algorithm */ + int (*check_swap)(void); +}; + +extern volatile int swap_init_ok; +int swap_init(void); +int swap_init_mm(struct mm_struct *mm); +int swap_tick_event(struct mm_struct *mm); +int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); +int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr); +int swap_out(struct mm_struct *mm, int n, int in_tick); +int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result); + +//#define MEMBER_OFFSET(m,t) ((int)(&((t *)0)->m)) +//#define FROM_MEMBER(m,t,a) ((t *)((char *)(a) - MEMBER_OFFSET(m,t))) + +#endif diff --git a/code/lab5/kern/mm/swap_fifo.c b/code/lab5/kern/mm/swap_fifo.c new file mode 100644 index 0000000..4cb00c1 --- /dev/null +++ b/code/lab5/kern/mm/swap_fifo.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out + * page replacement algorithm is a low-overhead algorithm that requires little book-keeping on + * the part of the operating system. The idea is obvious from the name - the operating system + * keeps track of all the pages in memory in a queue, with the most recent arrival at the back, + * and the earliest arrival in front. When a page needs to be replaced, the page at the front + * of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs + * poorly in practical application. Thus, it is rarely used in its unmodified form. This + * algorithm experiences Belady's anomaly. + * + * Details of FIFO PRA + * (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can + * link these pages into pra_list_head according the time order. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list + * implementation. You should know howto USE: list_init, list_add(list_add_after), + * list_add_before, list_del, list_next, list_prev. Another tricky method is to transform + * a general list struct to a special struct (such as struct page). You can find some MACRO: + * le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc. + */ + +list_entry_t pra_list_head; +/* + * (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head. + * Now, From the memory control struct mm_struct, we can access FIFO PRA + */ +static int +_fifo_init_mm(struct mm_struct *mm) +{ + list_init(&pra_list_head); + mm->sm_priv = &pra_list_head; + //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + return 0; +} +/* + * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue + */ +static int +_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + list_entry_t *entry=&(page->pra_page_link); + + assert(entry != NULL && head != NULL); + //record the page access situlation + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + return 0; +} +/* + * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, + * then set the addr of addr of this page to ptr_page. + */ +static int +_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + assert(head != NULL); + assert(in_tick==0); + /* Select the victim */ + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1) unlink the earliest arrival page in front of pra_list_head qeueue + //(2) set the addr of addr of this page to ptr_page + return 0; +} + +static int +_fifo_check_swap(void) { + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==4); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==4); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==4); + cprintf("write Virt Page e in fifo_check_swap\n"); + *(unsigned char *)0x5000 = 0x0e; + assert(pgfault_num==5); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==5); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==6); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==7); + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==8); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==9); + return 0; +} + + +static int +_fifo_init(void) +{ + return 0; +} + +static int +_fifo_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return 0; +} + +static int +_fifo_tick_event(struct mm_struct *mm) +{ return 0; } + + +struct swap_manager swap_manager_fifo = +{ + .name = "fifo swap manager", + .init = &_fifo_init, + .init_mm = &_fifo_init_mm, + .tick_event = &_fifo_tick_event, + .map_swappable = &_fifo_map_swappable, + .set_unswappable = &_fifo_set_unswappable, + .swap_out_victim = &_fifo_swap_out_victim, + .check_swap = &_fifo_check_swap, +}; diff --git a/code/lab5/kern/mm/swap_fifo.h b/code/lab5/kern/mm/swap_fifo.h new file mode 100644 index 0000000..1d74269 --- /dev/null +++ b/code/lab5/kern/mm/swap_fifo.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_SWAP_FIFO_H__ +#define __KERN_MM_SWAP_FIFO_H__ + +#include +extern struct swap_manager swap_manager_fifo; + +#endif diff --git a/code/lab5/kern/mm/vmm.c b/code/lab5/kern/mm/vmm.c new file mode 100644 index 0000000..8051479 --- /dev/null +++ b/code/lab5/kern/mm/vmm.c @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + vmm design include two parts: mm_struct (mm) & vma_struct (vma) + mm is the memory manager for the set of continuous virtual memory + area which have the same PDT. vma is a continuous virtual memory area. + There a linear link list for vma & a redblack link list for vma in mm. +--------------- + mm related functions: + golbal functions + struct mm_struct * mm_create(void) + void mm_destroy(struct mm_struct *mm) + int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) +-------------- + vma related functions: + global functions + struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) + void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) + struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) + local functions + inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) +--------------- + check correctness functions + void check_vmm(void); + void check_vma_struct(void); + void check_pgfault(void); +*/ + +static void check_vmm(void); +static void check_vma_struct(void); +static void check_pgfault(void); + +// mm_create - alloc a mm_struct & initialize it. +struct mm_struct * +mm_create(void) { + struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); + + if (mm != NULL) { + list_init(&(mm->mmap_list)); + mm->mmap_cache = NULL; + mm->pgdir = NULL; + mm->map_count = 0; + + if (swap_init_ok) swap_init_mm(mm); + else mm->sm_priv = NULL; + + set_mm_count(mm, 0); + lock_init(&(mm->mm_lock)); + } + return mm; +} + +// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +struct vma_struct * +vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); + + if (vma != NULL) { + vma->vm_start = vm_start; + vma->vm_end = vm_end; + vma->vm_flags = vm_flags; + } + return vma; +} + + +// find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +struct vma_struct * +find_vma(struct mm_struct *mm, uintptr_t addr) { + struct vma_struct *vma = NULL; + if (mm != NULL) { + vma = mm->mmap_cache; + if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { + bool found = 0; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + vma = le2vma(le, list_link); + if (addr < vma->vm_end) { + found = 1; + break; + } + } + if (!found) { + vma = NULL; + } + } + if (vma != NULL) { + mm->mmap_cache = vma; + } + } + return vma; +} + + +// check_vma_overlap - check if vma1 overlaps vma2 ? +static inline void +check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { + assert(prev->vm_start < prev->vm_end); + assert(prev->vm_end <= next->vm_start); + assert(next->vm_start < next->vm_end); +} + + +// insert_vma_struct -insert vma in mm's list link +void +insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + assert(vma->vm_start < vma->vm_end); + list_entry_t *list = &(mm->mmap_list); + list_entry_t *le_prev = list, *le_next; + + list_entry_t *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *mmap_prev = le2vma(le, list_link); + if (mmap_prev->vm_start > vma->vm_start) { + break; + } + le_prev = le; + } + + le_next = list_next(le_prev); + + /* check overlap */ + if (le_prev != list) { + check_vma_overlap(le2vma(le_prev, list_link), vma); + } + if (le_next != list) { + check_vma_overlap(vma, le2vma(le_next, list_link)); + } + + vma->vm_mm = mm; + list_add_after(le_prev, &(vma->list_link)); + + mm->map_count ++; +} + +// mm_destroy - free mm and mm internal fields +void +mm_destroy(struct mm_struct *mm) { + assert(mm_count(mm) == 0); + + list_entry_t *list = &(mm->mmap_list), *le; + while ((le = list_next(list)) != list) { + list_del(le); + kfree(le2vma(le, list_link)); //kfree vma + } + kfree(mm); //kfree mm + mm=NULL; +} + +int +mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store) { + uintptr_t start = ROUNDDOWN(addr, PGSIZE), end = ROUNDUP(addr + len, PGSIZE); + if (!USER_ACCESS(start, end)) { + return -E_INVAL; + } + + assert(mm != NULL); + + int ret = -E_INVAL; + + struct vma_struct *vma; + if ((vma = find_vma(mm, start)) != NULL && end > vma->vm_start) { + goto out; + } + ret = -E_NO_MEM; + + if ((vma = vma_create(start, end, vm_flags)) == NULL) { + goto out; + } + insert_vma_struct(mm, vma); + if (vma_store != NULL) { + *vma_store = vma; + } + ret = 0; + +out: + return ret; +} + +int +dup_mmap(struct mm_struct *to, struct mm_struct *from) { + assert(to != NULL && from != NULL); + list_entry_t *list = &(from->mmap_list), *le = list; + while ((le = list_prev(le)) != list) { + struct vma_struct *vma, *nvma; + vma = le2vma(le, list_link); + nvma = vma_create(vma->vm_start, vma->vm_end, vma->vm_flags); + if (nvma == NULL) { + return -E_NO_MEM; + } + + insert_vma_struct(to, nvma); + + bool share = 0; + if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share) != 0) { + return -E_NO_MEM; + } + } + return 0; +} + +void +exit_mmap(struct mm_struct *mm) { + assert(mm != NULL && mm_count(mm) == 0); + pde_t *pgdir = mm->pgdir; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + unmap_range(pgdir, vma->vm_start, vma->vm_end); + } + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + exit_range(pgdir, vma->vm_start, vma->vm_end); + } +} + +bool +copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable) { + if (!user_mem_check(mm, (uintptr_t)src, len, writable)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +bool +copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len) { + if (!user_mem_check(mm, (uintptr_t)dst, len, 1)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +// vmm_init - initialize virtual memory management +// - now just call check_vmm to check correctness of vmm +void +vmm_init(void) { + check_vmm(); +} + +// check_vmm - check correctness of vmm +static void +check_vmm(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_vma_struct(); + check_pgfault(); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vmm() succeeded.\n"); +} + +static void +check_vma_struct(void) { + size_t nr_free_pages_store = nr_free_pages(); + + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + int step1 = 10, step2 = step1 * 10; + + int i; + for (i = step1; i >= 0; i --) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + for (i = step1 + 1; i <= step2; i ++) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + list_entry_t *le = list_next(&(mm->mmap_list)); + + for (i = 0; i <= step2; i ++) { + assert(le != &(mm->mmap_list)); + struct vma_struct *mmap = le2vma(le, list_link); + assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); + le = list_next(le); + } + + for (i = 0; i < 5 * step2 + 2; i ++) { + struct vma_struct *vma = find_vma(mm, i); + assert(vma != NULL); + int j = i / 5; + if (i >= 5 * j + 2) { + j ++; + } + assert(vma->vm_start == j * 5 && vma->vm_end == j * 5 + 2); + } + + mm_destroy(mm); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vma_struct() succeeded!\n"); +} + +struct mm_struct *check_mm_struct; + +// check_pgfault - check correctness of pgfault handler +static void +check_pgfault(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_mm_struct = mm_create(); + assert(check_mm_struct != NULL); + + struct mm_struct *mm = check_mm_struct; + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + uintptr_t addr = 0x100; + assert(find_vma(mm, addr) == vma); + + int i, sum = 0; + for (i = 0; i < 100; i ++) { + *(char *)(addr + i) = i; + sum += i; + } + for (i = 0; i < 100; i ++) { + sum -= *(char *)(addr + i); + } + assert(sum == 0); + + page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + free_page(pa2page(pgdir[0])); + pgdir[0] = 0; + + mm->pgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_pgfault() succeeded!\n"); +} +//page fault number +volatile unsigned int pgfault_num=0; + +/* do_pgfault - interrupt handler to process the page fault execption + * @mm : the control struct for a set of vma using the same PDT + * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware + * @addr : the addr which causes a memory access exception, (the contents of the CR2 register) + * + * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault + * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing + * the exception and recovering from it. + * (1) The contents of the CR2 register. The processor loads the CR2 register with the + * 32-bit linear address that generated the exception. The do_pgfault fun can + * use this address to locate the corresponding page directory and page-table + * entries. + * (2) An error code on the kernel stack. The error code for a page fault has a format different from + * that for other exceptions. The error code tells the exception handler three things: + * -- The P flag (bit 0) indicates whether the exception was due to a not-present page (0) + * or to either an access rights violation or the use of a reserved bit (1). + * -- The W/R flag (bit 1) indicates whether the memory access that caused the exception + * was a read (0) or write (1). + * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) + * or supervisor mode (0) at the time of the exception. + */ +int +do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { + int ret = -E_INVAL; + //try to find a vma which include addr + struct vma_struct *vma = find_vma(mm, addr); + + pgfault_num++; + //If the addr is in the range of a mm's vma? + if (vma == NULL || vma->vm_start > addr) { + cprintf("not valid addr %x, and can not find it in vma\n", addr); + goto failed; + } + //check the error_code + switch (error_code & 3) { + default: + /* error code flag : default is 3 ( W/R=1, P=1): write, present */ + case 2: /* error code flag : (W/R=1, P=0): write, not present */ + if (!(vma->vm_flags & VM_WRITE)) { + cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); + goto failed; + } + break; + case 1: /* error code flag : (W/R=0, P=1): read, present */ + cprintf("do_pgfault failed: error code flag = read AND present\n"); + goto failed; + case 0: /* error code flag : (W/R=0, P=0): read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { + cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); + goto failed; + } + } + /* IF (write an existed addr ) OR + * (write an non_existed addr && addr is writable) OR + * (read an non_existed addr && addr is readable) + * THEN + * continue process + */ + uint32_t perm = PTE_U; + if (vma->vm_flags & VM_WRITE) { + perm |= PTE_W; + } + addr = ROUNDDOWN(addr, PGSIZE); + + ret = -E_NO_MEM; + + pte_t *ptep=NULL; + /*LAB3 EXERCISE 1: YOUR CODE + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * get_pte : get an pte and return the kernel virtual address of this pte for la + * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') + * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup + * an addr map pa<--->la with linear address la and the PDT pgdir + * DEFINES: + * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + * VARIABLES: + * mm->pgdir : the PDT of these vma + * + */ +#if 0 + /*LAB3 EXERCISE 1: YOUR CODE*/ + ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + if (*ptep == 0) { + //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + + } + else { + /*LAB3 EXERCISE 2: YOUR CODE + * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, + * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, + * find the addr of disk page, read the content of disk page into this memroy page + * page_insert : build the map of phy addr of an Page with the linear addr la + * swap_map_swappable : set the page swappable + */ + if(swap_init_ok) { + struct Page *page=NULL; + //(1)According to the mm AND addr, try to load the content of right disk page + // into the memory which page managed. + //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr + //(3) make the page swappable. + //(4) [NOTICE]: you myabe need to update your lab3's implementation for LAB5's normal execution. + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed; + } + } +#endif + ret = 0; +failed: + return ret; +} + +bool +user_mem_check(struct mm_struct *mm, uintptr_t addr, size_t len, bool write) { + if (mm != NULL) { + if (!USER_ACCESS(addr, addr + len)) { + return 0; + } + struct vma_struct *vma; + uintptr_t start = addr, end = addr + len; + while (start < end) { + if ((vma = find_vma(mm, start)) == NULL || start < vma->vm_start) { + return 0; + } + if (!(vma->vm_flags & ((write) ? VM_WRITE : VM_READ))) { + return 0; + } + if (write && (vma->vm_flags & VM_STACK)) { + if (start < vma->vm_start + PGSIZE) { //check stack start & size + return 0; + } + } + start = vma->vm_end; + } + return 1; + } + return KERN_ACCESS(addr, addr + len); +} + diff --git a/code/lab5/kern/mm/vmm.h b/code/lab5/kern/mm/vmm.h new file mode 100644 index 0000000..b2abfdd --- /dev/null +++ b/code/lab5/kern/mm/vmm.h @@ -0,0 +1,100 @@ +#ifndef __KERN_MM_VMM_H__ +#define __KERN_MM_VMM_H__ + +#include +#include +#include +#include + +//pre define +struct mm_struct; + +// the virtual continuous memory area(vma) +struct vma_struct { + struct mm_struct *vm_mm; // the set of vma using the same PDT + uintptr_t vm_start; // start addr of vma + uintptr_t vm_end; // end addr of vma + uint32_t vm_flags; // flags of vma + list_entry_t list_link; // linear list link which sorted by start addr of vma +}; + +#define le2vma(le, member) \ + to_struct((le), struct vma_struct, member) + +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 +#define VM_STACK 0x00000008 + +// the control struct for a set of vma using the same PDT +struct mm_struct { + list_entry_t mmap_list; // linear list link which sorted by start addr of vma + struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose + pde_t *pgdir; // the PDT of these vma + int map_count; // the count of these vma + void *sm_priv; // the private data for swap manager + atomic_t mm_count; // the number ofprocess which shared the mm + lock_t mm_lock; // mutex for using dup_mmap fun to duplicat the mm +}; + +struct vma_struct *find_vma(struct mm_struct *mm, uintptr_t addr); +struct vma_struct *vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags); +void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma); + +struct mm_struct *mm_create(void); +void mm_destroy(struct mm_struct *mm); + +void vmm_init(void); +int mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store); +int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr); + +int mm_unmap(struct mm_struct *mm, uintptr_t addr, size_t len); +int dup_mmap(struct mm_struct *to, struct mm_struct *from); +void exit_mmap(struct mm_struct *mm); +uintptr_t get_unmapped_area(struct mm_struct *mm, size_t len); +int mm_brk(struct mm_struct *mm, uintptr_t addr, size_t len); + +extern volatile unsigned int pgfault_num; +extern struct mm_struct *check_mm_struct; + +bool user_mem_check(struct mm_struct *mm, uintptr_t start, size_t len, bool write); +bool copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable); +bool copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len); + +static inline int +mm_count(struct mm_struct *mm) { + return atomic_read(&(mm->mm_count)); +} + +static inline void +set_mm_count(struct mm_struct *mm, int val) { + atomic_set(&(mm->mm_count), val); +} + +static inline int +mm_count_inc(struct mm_struct *mm) { + return atomic_add_return(&(mm->mm_count), 1); +} + +static inline int +mm_count_dec(struct mm_struct *mm) { + return atomic_sub_return(&(mm->mm_count), 1); +} + +static inline void +lock_mm(struct mm_struct *mm) { + if (mm != NULL) { + lock(&(mm->mm_lock)); + } +} + +static inline void +unlock_mm(struct mm_struct *mm) { + if (mm != NULL) { + unlock(&(mm->mm_lock)); + } +} + +#endif /* !__KERN_MM_VMM_H__ */ + diff --git a/code/lab5/kern/process/entry.S b/code/lab5/kern/process/entry.S new file mode 100644 index 0000000..7482e23 --- /dev/null +++ b/code/lab5/kern/process/entry.S @@ -0,0 +1,10 @@ +.text +.globl kernel_thread_entry +kernel_thread_entry: # void kernel_thread(void) + + pushl %edx # push arg + call *%ebx # call fn + + pushl %eax # save the return value of fn(arg) + call do_exit # call do_exit to terminate current thread + diff --git a/code/lab5/kern/process/proc.c b/code/lab5/kern/process/proc.c new file mode 100644 index 0000000..fb59893 --- /dev/null +++ b/code/lab5/kern/process/proc.c @@ -0,0 +1,841 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------- process/thread mechanism design&implementation ------------- +(an simplified Linux process/thread mechanism ) +introduction: + ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads +for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to +manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory). +------------------------------ +process state : meaning -- reason + PROC_UNINIT : uninitialized -- alloc_proc + PROC_SLEEPING : sleeping -- try_free_pages, do_wait, do_sleep + PROC_RUNNABLE : runnable(maybe running) -- proc_init, wakeup_proc, + PROC_ZOMBIE : almost dead -- do_exit + +----------------------------- +process state changing: + + alloc_proc RUNNING + + +--<----<--+ + + + proc_run + + V +-->---->--+ +PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING -- + A + + + | +--- do_exit --> PROC_ZOMBIE + + + + + -----------------------wakeup_proc---------------------------------- +----------------------------- +process relations +parent: proc->parent (proc is children) +children: proc->cptr (proc is parent) +older sibling: proc->optr (proc is younger sibling) +younger sibling: proc->yptr (proc is older sibling) +----------------------------- +related syscall for process: +SYS_exit : process exit, -->do_exit +SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc +SYS_wait : wait process -->do_wait +SYS_exec : after fork, process execute a program -->load a program and refresh the mm +SYS_clone : create child thread -->do_fork-->wakeup_proc +SYS_yield : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process +SYS_sleep : process sleep -->do_sleep +SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING + -->wakeup_proc-->do_wait-->do_exit +SYS_getpid : get the process's pid + +*/ + +// the process set's list +list_entry_t proc_list; + +#define HASH_SHIFT 10 +#define HASH_LIST_SIZE (1 << HASH_SHIFT) +#define pid_hashfn(x) (hash32(x, HASH_SHIFT)) + +// has list for process set based on pid +static list_entry_t hash_list[HASH_LIST_SIZE]; + +// idle proc +struct proc_struct *idleproc = NULL; +// init proc +struct proc_struct *initproc = NULL; +// current proc +struct proc_struct *current = NULL; + +static int nr_process = 0; + +void kernel_thread_entry(void); +void forkrets(struct trapframe *tf); +void switch_to(struct context *from, struct context *to); + +// alloc_proc - alloc a proc_struct and init all fields of proc_struct +static struct proc_struct * +alloc_proc(void) { + struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); + if (proc != NULL) { + //LAB4:EXERCISE1 YOUR CODE + /* + * below fields in proc_struct need to be initialized + * enum proc_state state; // Process state + * int pid; // Process ID + * int runs; // the running times of Proces + * uintptr_t kstack; // Process kernel stack + * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + * struct proc_struct *parent; // the parent process + * struct mm_struct *mm; // Process's memory management field + * struct context context; // Switch here to run process + * struct trapframe *tf; // Trap frame for current interrupt + * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + * uint32_t flags; // Process flag + * char name[PROC_NAME_LEN + 1]; // Process name + */ + } + return proc; +} + +// set_proc_name - set the name of proc +char * +set_proc_name(struct proc_struct *proc, const char *name) { + memset(proc->name, 0, sizeof(proc->name)); + return memcpy(proc->name, name, PROC_NAME_LEN); +} + +// get_proc_name - get the name of proc +char * +get_proc_name(struct proc_struct *proc) { + static char name[PROC_NAME_LEN + 1]; + memset(name, 0, sizeof(name)); + return memcpy(name, proc->name, PROC_NAME_LEN); +} + +// set_links - set the relation links of process +static void +set_links(struct proc_struct *proc) { + list_add(&proc_list, &(proc->list_link)); + proc->yptr = NULL; + if ((proc->optr = proc->parent->cptr) != NULL) { + proc->optr->yptr = proc; + } + proc->parent->cptr = proc; + nr_process ++; +} + +// remove_links - clean the relation links of process +static void +remove_links(struct proc_struct *proc) { + list_del(&(proc->list_link)); + if (proc->optr != NULL) { + proc->optr->yptr = proc->yptr; + } + if (proc->yptr != NULL) { + proc->yptr->optr = proc->optr; + } + else { + proc->parent->cptr = proc->optr; + } + nr_process --; +} + +// get_pid - alloc a unique pid for process +static int +get_pid(void) { + static_assert(MAX_PID > MAX_PROCESS); + struct proc_struct *proc; + list_entry_t *list = &proc_list, *le; + static int next_safe = MAX_PID, last_pid = MAX_PID; + if (++ last_pid >= MAX_PID) { + last_pid = 1; + goto inside; + } + if (last_pid >= next_safe) { + inside: + next_safe = MAX_PID; + repeat: + le = list; + while ((le = list_next(le)) != list) { + proc = le2proc(le, list_link); + if (proc->pid == last_pid) { + if (++ last_pid >= next_safe) { + if (last_pid >= MAX_PID) { + last_pid = 1; + } + next_safe = MAX_PID; + goto repeat; + } + } + else if (proc->pid > last_pid && next_safe > proc->pid) { + next_safe = proc->pid; + } + } + } + return last_pid; +} + +// proc_run - make process "proc" running on cpu +// NOTE: before call switch_to, should load base addr of "proc"'s new PDT +void +proc_run(struct proc_struct *proc) { + if (proc != current) { + bool intr_flag; + struct proc_struct *prev = current, *next = proc; + local_intr_save(intr_flag); + { + current = proc; + load_esp0(next->kstack + KSTACKSIZE); + lcr3(next->cr3); + switch_to(&(prev->context), &(next->context)); + } + local_intr_restore(intr_flag); + } +} + +// forkret -- the first kernel entry point of a new thread/process +// NOTE: the addr of forkret is setted in copy_thread function +// after switch_to, the current proc will execute here. +static void +forkret(void) { + forkrets(current->tf); +} + +// hash_proc - add proc into proc hash_list +static void +hash_proc(struct proc_struct *proc) { + list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); +} + +// unhash_proc - delete proc from proc hash_list +static void +unhash_proc(struct proc_struct *proc) { + list_del(&(proc->hash_link)); +} + +// find_proc - find proc frome proc hash_list according to pid +struct proc_struct * +find_proc(int pid) { + if (0 < pid && pid < MAX_PID) { + list_entry_t *list = hash_list + pid_hashfn(pid), *le = list; + while ((le = list_next(le)) != list) { + struct proc_struct *proc = le2proc(le, hash_link); + if (proc->pid == pid) { + return proc; + } + } + } + return NULL; +} + +// kernel_thread - create a kernel thread using "fn" function +// NOTE: the contents of temp trapframe tf will be copied to +// proc->tf in do_fork-->copy_thread function +int +kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { + struct trapframe tf; + memset(&tf, 0, sizeof(struct trapframe)); + tf.tf_cs = KERNEL_CS; + tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; + tf.tf_regs.reg_ebx = (uint32_t)fn; + tf.tf_regs.reg_edx = (uint32_t)arg; + tf.tf_eip = (uint32_t)kernel_thread_entry; + return do_fork(clone_flags | CLONE_VM, 0, &tf); +} + +// setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack +static int +setup_kstack(struct proc_struct *proc) { + struct Page *page = alloc_pages(KSTACKPAGE); + if (page != NULL) { + proc->kstack = (uintptr_t)page2kva(page); + return 0; + } + return -E_NO_MEM; +} + +// put_kstack - free the memory space of process kernel stack +static void +put_kstack(struct proc_struct *proc) { + free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE); +} + +// setup_pgdir - alloc one page as PDT +static int +setup_pgdir(struct mm_struct *mm) { + struct Page *page; + if ((page = alloc_page()) == NULL) { + return -E_NO_MEM; + } + pde_t *pgdir = page2kva(page); + memcpy(pgdir, boot_pgdir, PGSIZE); + pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_P | PTE_W; + mm->pgdir = pgdir; + return 0; +} + +// put_pgdir - free the memory space of PDT +static void +put_pgdir(struct mm_struct *mm) { + free_page(kva2page(mm->pgdir)); +} + +// copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags +// - if clone_flags & CLONE_VM, then "share" ; else "duplicate" +static int +copy_mm(uint32_t clone_flags, struct proc_struct *proc) { + struct mm_struct *mm, *oldmm = current->mm; + + /* current is a kernel thread */ + if (oldmm == NULL) { + return 0; + } + if (clone_flags & CLONE_VM) { + mm = oldmm; + goto good_mm; + } + + int ret = -E_NO_MEM; + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + + lock_mm(oldmm); + { + ret = dup_mmap(mm, oldmm); + } + unlock_mm(oldmm); + + if (ret != 0) { + goto bad_dup_cleanup_mmap; + } + +good_mm: + mm_count_inc(mm); + proc->mm = mm; + proc->cr3 = PADDR(mm->pgdir); + return 0; +bad_dup_cleanup_mmap: + exit_mmap(mm); + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + return ret; +} + +// copy_thread - setup the trapframe on the process's kernel stack top and +// - setup the kernel entry point and stack of process +static void +copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { + proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; + *(proc->tf) = *tf; + proc->tf->tf_regs.reg_eax = 0; + proc->tf->tf_esp = esp; + proc->tf->tf_eflags |= FL_IF; + + proc->context.eip = (uintptr_t)forkret; + proc->context.esp = (uintptr_t)(proc->tf); +} + +/* do_fork - parent process for a new child process + * @clone_flags: used to guide how to clone the child process + * @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread. + * @tf: the trapframe info, which will be copied to child process's proc->tf + */ +int +do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { + int ret = -E_NO_FREE_PROC; + struct proc_struct *proc; + if (nr_process >= MAX_PROCESS) { + goto fork_out; + } + ret = -E_NO_MEM; + //LAB4:EXERCISE2 YOUR CODE + /* + * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * alloc_proc: create a proc struct and init fields (lab4:exercise1) + * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack + * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags + * if clone_flags & CLONE_VM, then "share" ; else "duplicate" + * copy_thread: setup the trapframe on the process's kernel stack top and + * setup the kernel entry point and stack of process + * hash_proc: add proc into proc hash_list + * get_pid: alloc a unique pid for process + * wakup_proc: set proc->state = PROC_RUNNABLE + * VARIABLES: + * proc_list: the process set's list + * nr_process: the number of process set + */ + + // 1. call alloc_proc to allocate a proc_struct + // 2. call setup_kstack to allocate a kernel stack for child process + // 3. call copy_mm to dup OR share mm according clone_flag + // 4. call copy_thread to setup tf & context in proc_struct + // 5. insert proc_struct into hash_list && proc_list + // 6. call wakup_proc to make the new child process RUNNABLE + // 7. set ret vaule using child proc's pid +fork_out: + return ret; + +bad_fork_cleanup_kstack: + put_kstack(proc); +bad_fork_cleanup_proc: + kfree(proc); + goto fork_out; +} + +// do_exit - called by sys_exit +// 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process +// 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself. +// 3. call scheduler to switch to other process +int +do_exit(int error_code) { + if (current == idleproc) { + panic("idleproc exit.\n"); + } + if (current == initproc) { + panic("initproc exit.\n"); + } + + struct mm_struct *mm = current->mm; + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + current->state = PROC_ZOMBIE; + current->exit_code = error_code; + + bool intr_flag; + struct proc_struct *proc; + local_intr_save(intr_flag); + { + proc = current->parent; + if (proc->wait_state == WT_CHILD) { + wakeup_proc(proc); + } + while (current->cptr != NULL) { + proc = current->cptr; + current->cptr = proc->optr; + + proc->yptr = NULL; + if ((proc->optr = initproc->cptr) != NULL) { + initproc->cptr->yptr = proc; + } + proc->parent = initproc; + initproc->cptr = proc; + if (proc->state == PROC_ZOMBIE) { + if (initproc->wait_state == WT_CHILD) { + wakeup_proc(initproc); + } + } + } + } + local_intr_restore(intr_flag); + + schedule(); + panic("do_exit will not return!! %d.\n", current->pid); +} + +/* load_icode - load the content of binary program(ELF format) as the new content of current process + * @binary: the memory addr of the content of binary program + * @size: the size of the content of binary program + */ +static int +load_icode(unsigned char *binary, size_t size) { + if (current->mm != NULL) { + panic("load_icode: current->mm must be empty.\n"); + } + + int ret = -E_NO_MEM; + struct mm_struct *mm; + //(1) create a new mm for current process + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + //(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + //(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process + struct Page *page; + //(3.1) get the file header of the bianry program (ELF format) + struct elfhdr *elf = (struct elfhdr *)binary; + //(3.2) get the entry of the program section headers of the bianry program (ELF format) + struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff); + //(3.3) This program is valid? + if (elf->e_magic != ELF_MAGIC) { + ret = -E_INVAL_ELF; + goto bad_elf_cleanup_pgdir; + } + + uint32_t vm_flags, perm; + struct proghdr *ph_end = ph + elf->e_phnum; + for (; ph < ph_end; ph ++) { + //(3.4) find every program section headers + if (ph->p_type != ELF_PT_LOAD) { + continue ; + } + if (ph->p_filesz > ph->p_memsz) { + ret = -E_INVAL_ELF; + goto bad_cleanup_mmap; + } + if (ph->p_filesz == 0) { + continue ; + } + //(3.5) call mm_map fun to setup the new vma ( ph->p_va, ph->p_memsz) + vm_flags = 0, perm = PTE_U; + if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC; + if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE; + if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ; + if (vm_flags & VM_WRITE) perm |= PTE_W; + if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) { + goto bad_cleanup_mmap; + } + unsigned char *from = binary + ph->p_offset; + size_t off, size; + uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE); + + ret = -E_NO_MEM; + + //(3.6) alloc memory, and copy the contents of every program section (from, from+end) to process's memory (la, la+end) + end = ph->p_va + ph->p_filesz; + //(3.6.1) copy TEXT/DATA section of bianry program + while (start < end) { + if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { + goto bad_cleanup_mmap; + } + off = start - la, size = PGSIZE - off, la += PGSIZE; + if (end < la) { + size -= la - end; + } + memcpy(page2kva(page) + off, from, size); + start += size, from += size; + } + + //(3.6.2) build BSS section of binary program + end = ph->p_va + ph->p_memsz; + if (start < la) { + /* ph->p_memsz == ph->p_filesz */ + if (start == end) { + continue ; + } + off = start + PGSIZE - la, size = PGSIZE - off; + if (end < la) { + size -= la - end; + } + memset(page2kva(page) + off, 0, size); + start += size; + assert((end < la && start == end) || (end >= la && start == la)); + } + while (start < end) { + if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { + goto bad_cleanup_mmap; + } + off = start - la, size = PGSIZE - off, la += PGSIZE; + if (end < la) { + size -= la - end; + } + memset(page2kva(page) + off, 0, size); + start += size; + } + } + //(4) build user stack memory + vm_flags = VM_READ | VM_WRITE | VM_STACK; + if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) { + goto bad_cleanup_mmap; + } + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL); + + //(5) set current process's mm, sr3, and set CR3 reg = physical addr of Page Directory + mm_count_inc(mm); + current->mm = mm; + current->cr3 = PADDR(mm->pgdir); + lcr3(PADDR(mm->pgdir)); + + //(6) setup trapframe for user environment + struct trapframe *tf = current->tf; + memset(tf, 0, sizeof(struct trapframe)); + /* LAB5:EXERCISE1 YOUR CODE + * should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags + * NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So + * tf_cs should be USER_CS segment (see memlayout.h) + * tf_ds=tf_es=tf_ss should be USER_DS segment + * tf_esp should be the top addr of user stack (USTACKTOP) + * tf_eip should be the entry point of this binary program (elf->e_entry) + * tf_eflags should be set to enable computer to produce Interrupt + */ + ret = 0; +out: + return ret; +bad_cleanup_mmap: + exit_mmap(mm); +bad_elf_cleanup_pgdir: + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + goto out; +} + +// do_execve - call exit_mmap(mm)&pug_pgdir(mm) to reclaim memory space of current process +// - call load_icode to setup new memory space accroding binary prog. +int +do_execve(const char *name, size_t len, unsigned char *binary, size_t size) { + struct mm_struct *mm = current->mm; + if (!user_mem_check(mm, (uintptr_t)name, len, 0)) { + return -E_INVAL; + } + if (len > PROC_NAME_LEN) { + len = PROC_NAME_LEN; + } + + char local_name[PROC_NAME_LEN + 1]; + memset(local_name, 0, sizeof(local_name)); + memcpy(local_name, name, len); + + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + int ret; + if ((ret = load_icode(binary, size)) != 0) { + goto execve_exit; + } + set_proc_name(current, local_name); + return 0; + +execve_exit: + do_exit(ret); + panic("already exit: %e.\n", ret); +} + +// do_yield - ask the scheduler to reschedule +int +do_yield(void) { + current->need_resched = 1; + return 0; +} + +// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack +// - proc struct of this child. +// NOTE: only after do_wait function, all resources of the child proces are free. +int +do_wait(int pid, int *code_store) { + struct mm_struct *mm = current->mm; + if (code_store != NULL) { + if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) { + return -E_INVAL; + } + } + + struct proc_struct *proc; + bool intr_flag, haskid; +repeat: + haskid = 0; + if (pid != 0) { + proc = find_proc(pid); + if (proc != NULL && proc->parent == current) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + else { + proc = current->cptr; + for (; proc != NULL; proc = proc->optr) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + if (haskid) { + current->state = PROC_SLEEPING; + current->wait_state = WT_CHILD; + schedule(); + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + goto repeat; + } + return -E_BAD_PROC; + +found: + if (proc == idleproc || proc == initproc) { + panic("wait idleproc or initproc.\n"); + } + if (code_store != NULL) { + *code_store = proc->exit_code; + } + local_intr_save(intr_flag); + { + unhash_proc(proc); + remove_links(proc); + } + local_intr_restore(intr_flag); + put_kstack(proc); + kfree(proc); + return 0; +} + +// do_kill - kill process with pid by set this process's flags with PF_EXITING +int +do_kill(int pid) { + struct proc_struct *proc; + if ((proc = find_proc(pid)) != NULL) { + if (!(proc->flags & PF_EXITING)) { + proc->flags |= PF_EXITING; + if (proc->wait_state & WT_INTERRUPTED) { + wakeup_proc(proc); + } + return 0; + } + return -E_KILLED; + } + return -E_INVAL; +} + +// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread +static int +kernel_execve(const char *name, unsigned char *binary, size_t size) { + int ret, len = strlen(name); + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (len), "b" (binary), "D" (size) + : "memory"); + return ret; +} + +#define __KERNEL_EXECVE(name, binary, size) ({ \ + cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \ + current->pid, name); \ + kernel_execve(name, binary, (size_t)(size)); \ + }) + +#define KERNEL_EXECVE(x) ({ \ + extern unsigned char _binary_obj___user_##x##_out_start[], \ + _binary_obj___user_##x##_out_size[]; \ + __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start, \ + _binary_obj___user_##x##_out_size); \ + }) + +#define __KERNEL_EXECVE2(x, xstart, xsize) ({ \ + extern unsigned char xstart[], xsize[]; \ + __KERNEL_EXECVE(#x, xstart, (size_t)xsize); \ + }) + +#define KERNEL_EXECVE2(x, xstart, xsize) __KERNEL_EXECVE2(x, xstart, xsize) + +// user_main - kernel thread used to exec a user program +static int +user_main(void *arg) { +#ifdef TEST + KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE); +#else + KERNEL_EXECVE(exit); +#endif + panic("user_main execve failed.\n"); +} + +// init_main - the second kernel thread used to create user_main kernel threads +static int +init_main(void *arg) { + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = kallocated(); + + int pid = kernel_thread(user_main, NULL, 0); + if (pid <= 0) { + panic("create user_main failed.\n"); + } + + while (do_wait(0, NULL) == 0) { + schedule(); + } + + cprintf("all user-mode processes have quit.\n"); + assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL); + assert(nr_process == 2); + assert(list_next(&proc_list) == &(initproc->list_link)); + assert(list_prev(&proc_list) == &(initproc->list_link)); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == kallocated()); + cprintf("init check memory pass.\n"); + return 0; +} + +// proc_init - set up the first kernel thread idleproc "idle" by itself and +// - create the second kernel thread init_main +void +proc_init(void) { + int i; + + list_init(&proc_list); + for (i = 0; i < HASH_LIST_SIZE; i ++) { + list_init(hash_list + i); + } + + if ((idleproc = alloc_proc()) == NULL) { + panic("cannot alloc idleproc.\n"); + } + + idleproc->pid = 0; + idleproc->state = PROC_RUNNABLE; + idleproc->kstack = (uintptr_t)bootstack; + idleproc->need_resched = 1; + set_proc_name(idleproc, "idle"); + nr_process ++; + + current = idleproc; + + int pid = kernel_thread(init_main, NULL, 0); + if (pid <= 0) { + panic("create init_main failed.\n"); + } + + initproc = find_proc(pid); + set_proc_name(initproc, "init"); + + assert(idleproc != NULL && idleproc->pid == 0); + assert(initproc != NULL && initproc->pid == 1); +} + +// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works +void +cpu_idle(void) { + while (1) { + if (current->need_resched) { + schedule(); + } + } +} + diff --git a/code/lab5/kern/process/proc.h b/code/lab5/kern/process/proc.h new file mode 100644 index 0000000..a1f12d8 --- /dev/null +++ b/code/lab5/kern/process/proc.h @@ -0,0 +1,89 @@ +#ifndef __KERN_PROCESS_PROC_H__ +#define __KERN_PROCESS_PROC_H__ + +#include +#include +#include +#include + + +// process's state in his life cycle +enum proc_state { + PROC_UNINIT = 0, // uninitialized + PROC_SLEEPING, // sleeping + PROC_RUNNABLE, // runnable(maybe running) + PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource +}; + +// Saved registers for kernel context switches. +// Don't need to save all the %fs etc. segment registers, +// because they are constant across kernel contexts. +// Save all the regular registers so we don't need to care +// which are caller save, but not the return register %eax. +// (Not saving %eax just simplifies the switching code.) +// The layout of context must match code in switch.S. +struct context { + uint32_t eip; + uint32_t esp; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t esi; + uint32_t edi; + uint32_t ebp; +}; + +#define PROC_NAME_LEN 15 +#define MAX_PROCESS 4096 +#define MAX_PID (MAX_PROCESS * 2) + +extern list_entry_t proc_list; + +struct proc_struct { + enum proc_state state; // Process state + int pid; // Process ID + int runs; // the running times of Proces + uintptr_t kstack; // Process kernel stack + volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + struct proc_struct *parent; // the parent process + struct mm_struct *mm; // Process's memory management field + struct context context; // Switch here to run process + struct trapframe *tf; // Trap frame for current interrupt + uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + uint32_t flags; // Process flag + char name[PROC_NAME_LEN + 1]; // Process name + list_entry_t list_link; // Process link list + list_entry_t hash_link; // Process hash list + int exit_code; // exit code (be sent to parent proc) + uint32_t wait_state; // waiting state + struct proc_struct *cptr, *yptr, *optr; // relations between processes +}; + +#define PF_EXITING 0x00000001 // getting shutdown + +#define WT_CHILD (0x00000001 | WT_INTERRUPTED) +#define WT_INTERRUPTED 0x80000000 // the wait state could be interrupted + + +#define le2proc(le, member) \ + to_struct((le), struct proc_struct, member) + +extern struct proc_struct *idleproc, *initproc, *current; + +void proc_init(void); +void proc_run(struct proc_struct *proc); +int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags); + +char *set_proc_name(struct proc_struct *proc, const char *name); +char *get_proc_name(struct proc_struct *proc); +void cpu_idle(void) __attribute__((noreturn)); + +struct proc_struct *find_proc(int pid); +int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf); +int do_exit(int error_code); +int do_yield(void); +int do_execve(const char *name, size_t len, unsigned char *binary, size_t size); +int do_wait(int pid, int *code_store); +int do_kill(int pid); +#endif /* !__KERN_PROCESS_PROC_H__ */ + diff --git a/code/lab5/kern/process/switch.S b/code/lab5/kern/process/switch.S new file mode 100644 index 0000000..27b4c8c --- /dev/null +++ b/code/lab5/kern/process/switch.S @@ -0,0 +1,30 @@ +.text +.globl switch_to +switch_to: # switch_to(from, to) + + # save from's registers + movl 4(%esp), %eax # eax points to from + popl 0(%eax) # save eip !popl + movl %esp, 4(%eax) + movl %ebx, 8(%eax) + movl %ecx, 12(%eax) + movl %edx, 16(%eax) + movl %esi, 20(%eax) + movl %edi, 24(%eax) + movl %ebp, 28(%eax) + + # restore to's registers + movl 4(%esp), %eax # not 8(%esp): popped return address already + # eax now points to to + movl 28(%eax), %ebp + movl 24(%eax), %edi + movl 20(%eax), %esi + movl 16(%eax), %edx + movl 12(%eax), %ecx + movl 8(%eax), %ebx + movl 4(%eax), %esp + + pushl 0(%eax) # push eip + + ret + diff --git a/code/lab5/kern/schedule/sched.c b/code/lab5/kern/schedule/sched.c new file mode 100644 index 0000000..939caf4 --- /dev/null +++ b/code/lab5/kern/schedule/sched.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +void +wakeup_proc(struct proc_struct *proc) { + assert(proc->state != PROC_ZOMBIE); + bool intr_flag; + local_intr_save(intr_flag); + { + if (proc->state != PROC_RUNNABLE) { + proc->state = PROC_RUNNABLE; + proc->wait_state = 0; + } + else { + warn("wakeup runnable process.\n"); + } + } + local_intr_restore(intr_flag); +} + +void +schedule(void) { + bool intr_flag; + list_entry_t *le, *last; + struct proc_struct *next = NULL; + local_intr_save(intr_flag); + { + current->need_resched = 0; + last = (current == idleproc) ? &proc_list : &(current->list_link); + le = last; + do { + if ((le = list_next(le)) != &proc_list) { + next = le2proc(le, list_link); + if (next->state == PROC_RUNNABLE) { + break; + } + } + } while (le != last); + if (next == NULL || next->state != PROC_RUNNABLE) { + next = idleproc; + } + next->runs ++; + if (next != current) { + proc_run(next); + } + } + local_intr_restore(intr_flag); +} + diff --git a/code/lab5/kern/schedule/sched.h b/code/lab5/kern/schedule/sched.h new file mode 100644 index 0000000..ed1fc6e --- /dev/null +++ b/code/lab5/kern/schedule/sched.h @@ -0,0 +1,10 @@ +#ifndef __KERN_SCHEDULE_SCHED_H__ +#define __KERN_SCHEDULE_SCHED_H__ + +#include + +void schedule(void); +void wakeup_proc(struct proc_struct *proc); + +#endif /* !__KERN_SCHEDULE_SCHED_H__ */ + diff --git a/code/lab5/kern/sync/sync.h b/code/lab5/kern/sync/sync.h new file mode 100644 index 0000000..3e75192 --- /dev/null +++ b/code/lab5/kern/sync/sync.h @@ -0,0 +1,57 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +typedef volatile bool lock_t; + +static inline void +lock_init(lock_t *lock) { + *lock = 0; +} + +static inline bool +try_lock(lock_t *lock) { + return !test_and_set_bit(0, lock); +} + +static inline void +lock(lock_t *lock) { + while (!try_lock(lock)) { + schedule(); + } +} + +static inline void +unlock(lock_t *lock) { + if (!test_and_clear_bit(0, lock)) { + panic("Unlock failed.\n"); + } +} + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab5/kern/syscall/syscall.c b/code/lab5/kern/syscall/syscall.c new file mode 100644 index 0000000..7a7e4e2 --- /dev/null +++ b/code/lab5/kern/syscall/syscall.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include + +static int +sys_exit(uint32_t arg[]) { + int error_code = (int)arg[0]; + return do_exit(error_code); +} + +static int +sys_fork(uint32_t arg[]) { + struct trapframe *tf = current->tf; + uintptr_t stack = tf->tf_esp; + return do_fork(0, stack, tf); +} + +static int +sys_wait(uint32_t arg[]) { + int pid = (int)arg[0]; + int *store = (int *)arg[1]; + return do_wait(pid, store); +} + +static int +sys_exec(uint32_t arg[]) { + const char *name = (const char *)arg[0]; + size_t len = (size_t)arg[1]; + unsigned char *binary = (unsigned char *)arg[2]; + size_t size = (size_t)arg[3]; + return do_execve(name, len, binary, size); +} + +static int +sys_yield(uint32_t arg[]) { + return do_yield(); +} + +static int +sys_kill(uint32_t arg[]) { + int pid = (int)arg[0]; + return do_kill(pid); +} + +static int +sys_getpid(uint32_t arg[]) { + return current->pid; +} + +static int +sys_putc(uint32_t arg[]) { + int c = (int)arg[0]; + cputchar(c); + return 0; +} + +static int +sys_pgdir(uint32_t arg[]) { + print_pgdir(); + return 0; +} + +static int (*syscalls[])(uint32_t arg[]) = { + [SYS_exit] sys_exit, + [SYS_fork] sys_fork, + [SYS_wait] sys_wait, + [SYS_exec] sys_exec, + [SYS_yield] sys_yield, + [SYS_kill] sys_kill, + [SYS_getpid] sys_getpid, + [SYS_putc] sys_putc, + [SYS_pgdir] sys_pgdir, +}; + +#define NUM_SYSCALLS ((sizeof(syscalls)) / (sizeof(syscalls[0]))) + +void +syscall(void) { + struct trapframe *tf = current->tf; + uint32_t arg[5]; + int num = tf->tf_regs.reg_eax; + if (num >= 0 && num < NUM_SYSCALLS) { + if (syscalls[num] != NULL) { + arg[0] = tf->tf_regs.reg_edx; + arg[1] = tf->tf_regs.reg_ecx; + arg[2] = tf->tf_regs.reg_ebx; + arg[3] = tf->tf_regs.reg_edi; + arg[4] = tf->tf_regs.reg_esi; + tf->tf_regs.reg_eax = syscalls[num](arg); + return ; + } + } + print_trapframe(tf); + panic("undefined syscall %d, pid = %d, name = %s.\n", + num, current->pid, current->name); +} + diff --git a/code/lab5/kern/syscall/syscall.h b/code/lab5/kern/syscall/syscall.h new file mode 100644 index 0000000..a8fe843 --- /dev/null +++ b/code/lab5/kern/syscall/syscall.h @@ -0,0 +1,7 @@ +#ifndef __KERN_SYSCALL_SYSCALL_H__ +#define __KERN_SYSCALL_SYSCALL_H__ + +void syscall(void); + +#endif /* !__KERN_SYSCALL_SYSCALL_H__ */ + diff --git a/code/lab5/kern/trap/trap.c b/code/lab5/kern/trap/trap.c new file mode 100644 index 0000000..953b752 --- /dev/null +++ b/code/lab5/kern/trap/trap.c @@ -0,0 +1,289 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ + /* LAB5 YOUR CODE */ + //you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore + //so you should setup the syscall interrupt gate in here +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static inline void +print_pgfault(struct trapframe *tf) { + /* error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * bit 2 == 0 means kernel, 1 means user + * */ + cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), + (tf->tf_err & 4) ? 'U' : 'K', + (tf->tf_err & 2) ? 'W' : 'R', + (tf->tf_err & 1) ? "protection fault" : "no page found"); +} + +static int +pgfault_handler(struct trapframe *tf) { + extern struct mm_struct *check_mm_struct; + if(check_mm_struct !=NULL) { //used for test check_swap + print_pgfault(tf); + } + struct mm_struct *mm; + if (check_mm_struct != NULL) { + assert(current == idleproc); + mm = check_mm_struct; + } + else { + if (current == NULL) { + print_trapframe(tf); + print_pgfault(tf); + panic("unhandled page fault.\n"); + } + mm = current->mm; + } + return do_pgfault(mm, tf->tf_err, rcr2()); +} + +static volatile int in_swap_tick_event = 0; +extern struct mm_struct *check_mm_struct; + +static void +trap_dispatch(struct trapframe *tf) { + char c; + + int ret=0; + + switch (tf->tf_trapno) { + case T_PGFLT: //page fault + if ((ret = pgfault_handler(tf)) != 0) { + print_trapframe(tf); + if (current == NULL) { + panic("handle pgfault failed. ret=%d\n", ret); + } + else { + if (trap_in_kernel(tf)) { + panic("handle pgfault failed in kernel mode. ret=%d\n", ret); + } + cprintf("killed by kernel.\n"); + panic("handle user mode pgfault failed. ret=%d\n", ret); + do_exit(-E_KILLED); + } + } + break; + case T_SYSCALL: + syscall(); + break; + case IRQ_OFFSET + IRQ_TIMER: +#if 0 + LAB3 : If some page replacement algorithm need tick to change the priority of pages, + then you can add code here. +#endif + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + /* LAB5 YOUR CODE */ + /* you should upate you lab1 code (just add ONE or TWO lines of code): + * Every TICK_NUM cycle, you should set current process's current->need_resched = 1 + */ + + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + print_trapframe(tf); + if (current != NULL) { + cprintf("unhandled trap.\n"); + do_exit(-E_KILLED); + } + // in kernel, it must be a mistake + panic("unexpected trap in kernel.\n"); + + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + // used for previous projects + if (current == NULL) { + trap_dispatch(tf); + } + else { + // keep a trapframe chain in stack + struct trapframe *otf = current->tf; + current->tf = tf; + + bool in_kernel = trap_in_kernel(tf); + + trap_dispatch(tf); + + current->tf = otf; + if (!in_kernel) { + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + if (current->need_resched) { + schedule(); + } + } + } +} + diff --git a/code/lab5/kern/trap/trap.h b/code/lab5/kern/trap/trap.h new file mode 100644 index 0000000..e870a6f --- /dev/null +++ b/code/lab5/kern/trap/trap.h @@ -0,0 +1,89 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab5/kern/trap/trapentry.S b/code/lab5/kern/trap/trapentry.S new file mode 100644 index 0000000..3565ec8 --- /dev/null +++ b/code/lab5/kern/trap/trapentry.S @@ -0,0 +1,49 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + +.globl forkrets +forkrets: + # set stack to this new process's trapframe + movl 4(%esp), %esp + jmp __trapret diff --git a/code/lab5/kern/trap/vectors.S b/code/lab5/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab5/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab5/libs/atomic.h b/code/lab5/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab5/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab5/libs/defs.h b/code/lab5/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab5/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab5/libs/elf.h b/code/lab5/libs/elf.h new file mode 100644 index 0000000..8678f10 --- /dev/null +++ b/code/lab5/libs/elf.h @@ -0,0 +1,48 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +/* values for Proghdr::p_type */ +#define ELF_PT_LOAD 1 + +/* flag bits for Proghdr::p_flags */ +#define ELF_PF_X 1 +#define ELF_PF_W 2 +#define ELF_PF_R 4 + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab5/libs/error.h b/code/lab5/libs/error.h new file mode 100644 index 0000000..ddad593 --- /dev/null +++ b/code/lab5/libs/error.h @@ -0,0 +1,33 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault +#define E_SWAP_FAULT 7 // SWAP READ/WRITE fault +#define E_INVAL_ELF 8 // Invalid elf file +#define E_KILLED 9 // Process is killed +#define E_PANIC 10 // Panic Failure +#define E_TIMEOUT 11 // Timeout +#define E_TOO_BIG 12 // Argument is Too Big +#define E_NO_DEV 13 // No such Device +#define E_NA_DEV 14 // Device Not Available +#define E_BUSY 15 // Device/File is Busy +#define E_NOENT 16 // No Such File or Directory +#define E_ISDIR 17 // Is a Directory +#define E_NOTDIR 18 // Not a Directory +#define E_XDEV 19 // Cross Device-Link +#define E_UNIMP 20 // Unimplemented Feature +#define E_SEEK 21 // Illegal Seek +#define E_MAX_OPEN 22 // Too Many Files are Open +#define E_EXISTS 23 // File/Directory Already Exists +#define E_NOTEMPTY 24 // Directory is Not Empty +/* the maximum allowed */ +#define MAXERROR 24 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab5/libs/hash.c b/code/lab5/libs/hash.c new file mode 100644 index 0000000..61bcd88 --- /dev/null +++ b/code/lab5/libs/hash.c @@ -0,0 +1,18 @@ +#include + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL + +/* * + * hash32 - generate a hash value in the range [0, 2^@bits - 1] + * @val: the input value + * @bits: the number of bits in a return value + * + * High bits are more random, so we use them. + * */ +uint32_t +hash32(uint32_t val, unsigned int bits) { + uint32_t hash = val * GOLDEN_RATIO_PRIME_32; + return (hash >> (32 - bits)); +} + diff --git a/code/lab5/libs/list.h b/code/lab5/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab5/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab5/libs/printfmt.c b/code/lab5/libs/printfmt.c new file mode 100644 index 0000000..a666a52 --- /dev/null +++ b/code/lab5/libs/printfmt.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", + [E_INVAL_ELF] "invalid elf file", + [E_KILLED] "process is killed", + [E_PANIC] "panic failure", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab5/libs/rand.c b/code/lab5/libs/rand.c new file mode 100644 index 0000000..2a2c4e7 --- /dev/null +++ b/code/lab5/libs/rand.c @@ -0,0 +1,26 @@ +#include +#include + +static unsigned long long next = 1; + +/* * + * rand - returns a pseudo-random integer + * + * The rand() function return a value in the range [0, RAND_MAX]. + * */ +int +rand(void) { + next = (next * 0x5DEECE66DLL + 0xBLL) & ((1LL << 48) - 1); + unsigned long long result = (next >> 12); + return (int)do_div(result, RAND_MAX + 1); +} + +/* * + * srand - seed the random number generator with the given number + * @seed: the required seed number + * */ +void +srand(unsigned int seed) { + next = seed; +} + diff --git a/code/lab5/libs/stdarg.h b/code/lab5/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab5/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab5/libs/stdio.h b/code/lab5/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab5/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab5/libs/stdlib.h b/code/lab5/libs/stdlib.h new file mode 100644 index 0000000..51878ef --- /dev/null +++ b/code/lab5/libs/stdlib.h @@ -0,0 +1,17 @@ +#ifndef __LIBS_STDLIB_H__ +#define __LIBS_STDLIB_H__ + +#include + +/* the largest number rand will return */ +#define RAND_MAX 2147483647UL + +/* libs/rand.c */ +int rand(void); +void srand(unsigned int seed); + +/* libs/hash.c */ +uint32_t hash32(uint32_t val, unsigned int bits); + +#endif /* !__LIBS_RAND_H__ */ + diff --git a/code/lab5/libs/string.c b/code/lab5/libs/string.c new file mode 100644 index 0000000..bcf1b1c --- /dev/null +++ b/code/lab5/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab5/libs/string.h b/code/lab5/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab5/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab5/libs/unistd.h b/code/lab5/libs/unistd.h new file mode 100644 index 0000000..56ce6f5 --- /dev/null +++ b/code/lab5/libs/unistd.h @@ -0,0 +1,29 @@ +#ifndef __LIBS_UNISTD_H__ +#define __LIBS_UNISTD_H__ + +#define T_SYSCALL 0x80 + +/* syscall number */ +#define SYS_exit 1 +#define SYS_fork 2 +#define SYS_wait 3 +#define SYS_exec 4 +#define SYS_clone 5 +#define SYS_yield 10 +#define SYS_sleep 11 +#define SYS_kill 12 +#define SYS_gettime 17 +#define SYS_getpid 18 +#define SYS_brk 19 +#define SYS_mmap 20 +#define SYS_munmap 21 +#define SYS_shmem 22 +#define SYS_putc 30 +#define SYS_pgdir 31 + +/* SYS_fork flags */ +#define CLONE_VM 0x00000100 // set if VM shared between processes +#define CLONE_THREAD 0x00000200 // thread group + +#endif /* !__LIBS_UNISTD_H__ */ + diff --git a/code/lab5/libs/x86.h b/code/lab5/libs/x86.h new file mode 100644 index 0000000..b29f671 --- /dev/null +++ b/code/lab5/libs/x86.h @@ -0,0 +1,302 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab5/tools/boot.ld b/code/lab5/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab5/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab5/tools/function.mk b/code/lab5/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab5/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab5/tools/gdbinit b/code/lab5/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab5/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab5/tools/grade.sh b/code/lab5/tools/grade.sh new file mode 100644 index 0000000..54df4f2 --- /dev/null +++ b/code/lab5/tools/grade.sh @@ -0,0 +1,517 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-prog ] [-Ddef...] [-check ] checkargs ... + tag= + prog= + check=check_regexps + while true; do + select= + case $1 in + -tag|-prog) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + if [ -z "$prog" ]; then + $make $makeopts touch > /dev/null 2>&1 + args="$defs" + else + if [ -z "$tag" ]; then + tag="$prog" + fi + args="build-$prog $defs" + fi + + build_run "$tag" "$args" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## swap image +swapimg=$(make_print swapimg) + +## set default qemu-options +qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback" + +## set break-function, default is readline +brkfun=readline + +default_check() { + pts=7 + check_regexps "$@" + + pts=3 + quick_check 'check output' \ + 'memory management: default_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' \ + 'check_slab() succeeded!' \ + 'check_vma_struct() succeeded!' \ + 'page fault at 0x00000100: K/W [no page found].' \ + 'check_pgfault() succeeded!' \ + 'check_vmm() succeeded.' \ + 'page fault at 0x00001000: K/W [no page found].' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'write Virt Page e in fifo_check_swap' \ + 'page fault at 0x00005000: K/W [no page found].' \ + 'page fault at 0x00001000: K/W [no page found]' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'check_swap() succeeded!' \ + '++ setup timer interrupts' +} + +## check now!! + +run_test -prog 'badsegment' -check default_check \ + 'kernel_execve: pid = 2, name = "badsegment".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000028' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'divzero' -check default_check \ + 'kernel_execve: pid = 2, name = "divzero".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x00000000 Divide error' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'softint' -check default_check \ + 'kernel_execve: pid = 2, name = "softint".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000072' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'faultread' -check default_check \ + 'kernel_execve: pid = 2, name = "faultread".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000004' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'faultreadkernel' -check default_check \ + 'kernel_execve: pid = 2, name = "faultreadkernel".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000005' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'hello' -check default_check \ + 'kernel_execve: pid = 2, name = "hello".' \ + 'Hello world!!.' \ + 'I am process 2.' \ + 'hello pass.' + +run_test -prog 'testbss' -check default_check \ + 'kernel_execve: pid = 2, name = "testbss".' \ + 'Making sure bss works right...' \ + 'Yes, good. Now doing a wild write off the end...' \ + 'testbss may pass.' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000006' \ + - ' eip 0x008.....' \ + 'killed by kernel.' \ + ! - 'user panic at .*' + +run_test -prog 'pgdir' -check default_check \ + 'kernel_execve: pid = 2, name = "pgdir".' \ + 'I am 2, print pgdir.' \ + 'PDE(001) 00800000-00c00000 00400000 urw' \ + ' |-- PTE(00002) 00800000-00802000 00002000 ur-' \ + ' |-- PTE(00001) 00802000-00803000 00001000 urw' \ + 'PDE(001) afc00000-b0000000 00400000 urw' \ + ' |-- PTE(00004) afffc000-b0000000 00004000 urw' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'pgdir pass.' + +run_test -prog 'yield' -check default_check \ + 'kernel_execve: pid = 2, name = "yield".' \ + 'Hello, I am process 2.' \ + 'Back in process 2, iteration 0.' \ + 'Back in process 2, iteration 1.' \ + 'Back in process 2, iteration 2.' \ + 'Back in process 2, iteration 3.' \ + 'Back in process 2, iteration 4.' \ + 'All done in process 2.' \ + 'yield pass.' + + +run_test -prog 'badarg' -check default_check \ + 'kernel_execve: pid = 2, name = "badarg".' \ + 'fork ok.' \ + 'badarg pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'exit' -check default_check \ + 'kernel_execve: pid = 2, name = "exit".' \ + 'I am the parent. Forking the child...' \ + 'I am the parent, waiting now..' \ + 'I am the child.' \ + - 'waitpid [0-9]+ ok\.' \ + 'exit pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'spin' -check default_check \ + 'kernel_execve: pid = 2, name = "spin".' \ + 'I am the parent. Forking the child...' \ + 'I am the parent. Running the child...' \ + 'I am the child. spinning ...' \ + 'I am the parent. Killing the child...' \ + 'kill returns 0' \ + 'wait returns 0' \ + 'spin may pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'waitkill' -check default_check \ + 'kernel_execve: pid = 2, name = "waitkill".' \ + 'wait child 1.' \ + 'child 2.' \ + 'child 1.' \ + 'kill parent ok.' \ + 'kill child1 ok.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=15 + +run_test -prog 'forktest' -check default_check \ + 'kernel_execve: pid = 2, name = "forktest".' \ + 'I am child 31' \ + 'I am child 19' \ + 'I am child 13' \ + 'I am child 0' \ + 'forktest pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'fork claimed to work [0-9]+ times!' \ + ! 'wait stopped early' \ + ! 'wait got too many' \ + ! - 'user panic at .*' + +## print final-score +show_final + diff --git a/code/lab5/tools/kernel.ld b/code/lab5/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab5/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab5/tools/sign.c b/code/lab5/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab5/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab5/tools/user.ld b/code/lab5/tools/user.ld new file mode 100644 index 0000000..c73b6fa --- /dev/null +++ b/code/lab5/tools/user.ld @@ -0,0 +1,71 @@ +/* Simple linker script for ucore user-level programs. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS { + /* Load programs at this address: "." means the current address */ + . = 0x800020; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + + /* Place debugging symbols so that they can be found by + * the kernel debugger. + * Specifically, the four words at 0x200000 mark the beginning of + * the stabs, the end of the stabs, the beginning of the stabs + * string table, and the end of the stabs string table, respectively. + */ + + .stab_info 0x200000 : { + LONG(__STAB_BEGIN__); + LONG(__STAB_END__); + LONG(__STABSTR_BEGIN__); + LONG(__STABSTR_END__); + } + + .stab : { + __STAB_BEGIN__ = DEFINED(__STAB_BEGIN__) ? __STAB_BEGIN__ : .; + *(.stab); + __STAB_END__ = DEFINED(__STAB_END__) ? __STAB_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + __STABSTR_BEGIN__ = DEFINED(__STABSTR_BEGIN__) ? __STABSTR_BEGIN__ : .; + *(.stabstr); + __STABSTR_END__ = DEFINED(__STABSTR_END__) ? __STABSTR_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack .comment) + } +} diff --git a/code/lab5/tools/vector.c b/code/lab5/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab5/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab5/user/badarg.c b/code/lab5/user/badarg.c new file mode 100644 index 0000000..7b4ffad --- /dev/null +++ b/code/lab5/user/badarg.c @@ -0,0 +1,22 @@ +#include +#include + +int +main(void) { + int pid, exit_code; + if ((pid = fork()) == 0) { + cprintf("fork ok.\n"); + int i; + for (i = 0; i < 10; i ++) { + yield(); + } + exit(0xbeaf); + } + assert(pid > 0); + assert(waitpid(-1, NULL) != 0); + assert(waitpid(pid, (void *)0xC0000000) != 0); + assert(waitpid(pid, &exit_code) == 0 && exit_code == 0xbeaf); + cprintf("badarg pass.\n"); + return 0; +} + diff --git a/code/lab5/user/badsegment.c b/code/lab5/user/badsegment.c new file mode 100644 index 0000000..cdd9e99 --- /dev/null +++ b/code/lab5/user/badsegment.c @@ -0,0 +1,11 @@ +#include +#include + +/* try to load the kernel's TSS selector into the DS register */ + +int +main(void) { + asm volatile("movw $0x28,%ax; movw %ax,%ds"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/divzero.c b/code/lab5/user/divzero.c new file mode 100644 index 0000000..16c23d5 --- /dev/null +++ b/code/lab5/user/divzero.c @@ -0,0 +1,11 @@ +#include +#include + +int zero; + +int +main(void) { + cprintf("value is %d.\n", 1 / zero); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/exit.c b/code/lab5/user/exit.c new file mode 100644 index 0000000..c3ac5f8 --- /dev/null +++ b/code/lab5/user/exit.c @@ -0,0 +1,34 @@ +#include +#include + +int magic = -0x10384; + +int +main(void) { + int pid, code; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child.\n"); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + exit(magic); + } + else { + cprintf("I am parent, fork a child pid %d\n",pid); + } + assert(pid > 0); + cprintf("I am the parent, waiting now..\n"); + + assert(waitpid(pid, &code) == 0 && code == magic); + assert(waitpid(pid, &code) != 0 && wait() != 0); + cprintf("waitpid %d ok.\n", pid); + + cprintf("exit pass.\n"); + return 0; +} + diff --git a/code/lab5/user/faultread.c b/code/lab5/user/faultread.c new file mode 100644 index 0000000..6d292e1 --- /dev/null +++ b/code/lab5/user/faultread.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %8x from 0.\n", *(unsigned int *)0); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/faultreadkernel.c b/code/lab5/user/faultreadkernel.c new file mode 100644 index 0000000..53457f6 --- /dev/null +++ b/code/lab5/user/faultreadkernel.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %08x from 0xfac00000!\n", *(unsigned *)0xfac00000); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/forktest.c b/code/lab5/user/forktest.c new file mode 100644 index 0000000..3eda228 --- /dev/null +++ b/code/lab5/user/forktest.c @@ -0,0 +1,34 @@ +#include +#include + +const int max_child = 32; + +int +main(void) { + int n, pid; + for (n = 0; n < max_child; n ++) { + if ((pid = fork()) == 0) { + cprintf("I am child %d\n", n); + exit(0); + } + assert(pid > 0); + } + + if (n > max_child) { + panic("fork claimed to work %d times!\n", n); + } + + for (; n > 0; n --) { + if (wait() != 0) { + panic("wait stopped early\n"); + } + } + + if (wait() == 0) { + panic("wait got too many\n"); + } + + cprintf("forktest pass.\n"); + return 0; +} + diff --git a/code/lab5/user/forktree.c b/code/lab5/user/forktree.c new file mode 100644 index 0000000..ad45bc1 --- /dev/null +++ b/code/lab5/user/forktree.c @@ -0,0 +1,37 @@ +#include +#include +#include + +#define DEPTH 2 + +void forktree(const char *cur); + +void +forkchild(const char *cur, char branch) { + char nxt[DEPTH + 1]; + + if (strlen(cur) >= DEPTH) + return; + + snprintf(nxt, DEPTH + 1, "%s%c", cur, branch); + if (fork() == 0) { + forktree(nxt); + yield(); + exit(0); + } +} + +void +forktree(const char *cur) { + cprintf("%04x: I am '%s'\n", getpid(), cur); + + forkchild(cur, '0'); + forkchild(cur, '1'); +} + +int +main(void) { + forktree(""); + return 0; +} + diff --git a/code/lab5/user/hello.c b/code/lab5/user/hello.c new file mode 100644 index 0000000..0f05251 --- /dev/null +++ b/code/lab5/user/hello.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("Hello world!!.\n"); + cprintf("I am process %d.\n", getpid()); + cprintf("hello pass.\n"); + return 0; +} + diff --git a/code/lab5/user/libs/initcode.S b/code/lab5/user/libs/initcode.S new file mode 100644 index 0000000..77a91d6 --- /dev/null +++ b/code/lab5/user/libs/initcode.S @@ -0,0 +1,14 @@ +.text +.globl _start +_start: + # set ebp for backtrace + movl $0x0, %ebp + + # move down the esp register + # since it may cause page fault in backtrace + subl $0x20, %esp + + # call user-program function + call umain +1: jmp 1b + diff --git a/code/lab5/user/libs/panic.c b/code/lab5/user/libs/panic.c new file mode 100644 index 0000000..783be16 --- /dev/null +++ b/code/lab5/user/libs/panic.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +void +__panic(const char *file, int line, const char *fmt, ...) { + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("user panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + exit(-E_PANIC); +} + +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("user warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + diff --git a/code/lab5/user/libs/stdio.c b/code/lab5/user/libs/stdio.c new file mode 100644 index 0000000..69b7749 --- /dev/null +++ b/code/lab5/user/libs/stdio.c @@ -0,0 +1,62 @@ +#include +#include +#include + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + sys_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + int cnt = vcprintf(fmt, ap); + va_end(ap); + + return cnt; +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + diff --git a/code/lab5/user/libs/syscall.c b/code/lab5/user/libs/syscall.c new file mode 100644 index 0000000..7bdc0d2 --- /dev/null +++ b/code/lab5/user/libs/syscall.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +#define MAX_ARGS 5 + +static inline int +syscall(int num, ...) { + va_list ap; + va_start(ap, num); + uint32_t a[MAX_ARGS]; + int i, ret; + for (i = 0; i < MAX_ARGS; i ++) { + a[i] = va_arg(ap, uint32_t); + } + va_end(ap); + + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), + "a" (num), + "d" (a[0]), + "c" (a[1]), + "b" (a[2]), + "D" (a[3]), + "S" (a[4]) + : "cc", "memory"); + return ret; +} + +int +sys_exit(int error_code) { + return syscall(SYS_exit, error_code); +} + +int +sys_fork(void) { + return syscall(SYS_fork); +} + +int +sys_wait(int pid, int *store) { + return syscall(SYS_wait, pid, store); +} + +int +sys_yield(void) { + return syscall(SYS_yield); +} + +int +sys_kill(int pid) { + return syscall(SYS_kill, pid); +} + +int +sys_getpid(void) { + return syscall(SYS_getpid); +} + +int +sys_putc(int c) { + return syscall(SYS_putc, c); +} + +int +sys_pgdir(void) { + return syscall(SYS_pgdir); +} + diff --git a/code/lab5/user/libs/syscall.h b/code/lab5/user/libs/syscall.h new file mode 100644 index 0000000..1ccfbc4 --- /dev/null +++ b/code/lab5/user/libs/syscall.h @@ -0,0 +1,14 @@ +#ifndef __USER_LIBS_SYSCALL_H__ +#define __USER_LIBS_SYSCALL_H__ + +int sys_exit(int error_code); +int sys_fork(void); +int sys_wait(int pid, int *store); +int sys_yield(void); +int sys_kill(int pid); +int sys_getpid(void); +int sys_putc(int c); +int sys_pgdir(void); + +#endif /* !__USER_LIBS_SYSCALL_H__ */ + diff --git a/code/lab5/user/libs/ulib.c b/code/lab5/user/libs/ulib.c new file mode 100644 index 0000000..329da5c --- /dev/null +++ b/code/lab5/user/libs/ulib.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +void +exit(int error_code) { + sys_exit(error_code); + cprintf("BUG: exit failed.\n"); + while (1); +} + +int +fork(void) { + return sys_fork(); +} + +int +wait(void) { + return sys_wait(0, NULL); +} + +int +waitpid(int pid, int *store) { + return sys_wait(pid, store); +} + +void +yield(void) { + sys_yield(); +} + +int +kill(int pid) { + return sys_kill(pid); +} + +int +getpid(void) { + return sys_getpid(); +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + sys_pgdir(); +} + diff --git a/code/lab5/user/libs/ulib.h b/code/lab5/user/libs/ulib.h new file mode 100644 index 0000000..1bb5763 --- /dev/null +++ b/code/lab5/user/libs/ulib.h @@ -0,0 +1,36 @@ +#ifndef __USER_LIBS_ULIB_H__ +#define __USER_LIBS_ULIB_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +void __noreturn exit(int error_code); +int fork(void); +int wait(void); +int waitpid(int pid, int *store); +void yield(void); +int kill(int pid); +int getpid(void); +void print_pgdir(void); + +#endif /* !__USER_LIBS_ULIB_H__ */ + diff --git a/code/lab5/user/libs/umain.c b/code/lab5/user/libs/umain.c new file mode 100644 index 0000000..c352072 --- /dev/null +++ b/code/lab5/user/libs/umain.c @@ -0,0 +1,10 @@ +#include + +int main(void); + +void +umain(void) { + int ret = main(); + exit(ret); +} + diff --git a/code/lab5/user/pgdir.c b/code/lab5/user/pgdir.c new file mode 100644 index 0000000..09fd7e3 --- /dev/null +++ b/code/lab5/user/pgdir.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("I am %d, print pgdir.\n", getpid()); + print_pgdir(); + cprintf("pgdir pass.\n"); + return 0; +} + diff --git a/code/lab5/user/softint.c b/code/lab5/user/softint.c new file mode 100644 index 0000000..2f14d15 --- /dev/null +++ b/code/lab5/user/softint.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + asm volatile("int $14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/spin.c b/code/lab5/user/spin.c new file mode 100644 index 0000000..91581e5 --- /dev/null +++ b/code/lab5/user/spin.c @@ -0,0 +1,29 @@ +#include +#include + +int +main(void) { + int pid, ret; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child. spinning ...\n"); + while (1); + } + cprintf("I am the parent. Running the child...\n"); + + yield(); + yield(); + yield(); + + cprintf("I am the parent. Killing the child...\n"); + + assert((ret = kill(pid)) == 0); + cprintf("kill returns %d\n", ret); + + assert((ret = waitpid(pid, NULL)) == 0); + cprintf("wait returns %d\n", ret); + + cprintf("spin may pass.\n"); + return 0; +} + diff --git a/code/lab5/user/testbss.c b/code/lab5/user/testbss.c new file mode 100644 index 0000000..14dc6e1 --- /dev/null +++ b/code/lab5/user/testbss.c @@ -0,0 +1,33 @@ +#include +#include + +#define ARRAYSIZE (1024*1024) + +uint32_t bigarray[ARRAYSIZE]; + +int +main(void) { + cprintf("Making sure bss works right...\n"); + int i; + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != 0) { + panic("bigarray[%d] isn't cleared!\n", i); + } + } + for (i = 0; i < ARRAYSIZE; i ++) { + bigarray[i] = i; + } + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != i) { + panic("bigarray[%d] didn't hold its value!\n", i); + } + } + + cprintf("Yes, good. Now doing a wild write off the end...\n"); + cprintf("testbss may pass.\n"); + + bigarray[ARRAYSIZE + 1024] = 0; + asm volatile ("int $0x14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/waitkill.c b/code/lab5/user/waitkill.c new file mode 100644 index 0000000..9bb3f80 --- /dev/null +++ b/code/lab5/user/waitkill.c @@ -0,0 +1,59 @@ +#include +#include + +void +do_yield(void) { + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); +} + +int parent, pid1, pid2; + +void +loop(void) { + cprintf("child 1.\n"); + while (1); +} + +void +work(void) { + cprintf("child 2.\n"); + do_yield(); + if (kill(parent) == 0) { + cprintf("kill parent ok.\n"); + do_yield(); + if (kill(pid1) == 0) { + cprintf("kill child1 ok.\n"); + exit(0); + } + } + exit(-1); +} + +int +main(void) { + parent = getpid(); + if ((pid1 = fork()) == 0) { + loop(); + } + + assert(pid1 > 0); + + if ((pid2 = fork()) == 0) { + work(); + } + if (pid2 > 0) { + cprintf("wait child 1.\n"); + waitpid(pid1, NULL); + panic("waitpid %d returns\n", pid1); + } + else { + kill(pid1); + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab5/user/yield.c b/code/lab5/user/yield.c new file mode 100644 index 0000000..a19890d --- /dev/null +++ b/code/lab5/user/yield.c @@ -0,0 +1,16 @@ +#include +#include + +int +main(void) { + int i; + cprintf("Hello, I am process %d.\n", getpid()); + for (i = 0; i < 5; i ++) { + yield(); + cprintf("Back in process %d, iteration %d.\n", getpid(), i); + } + cprintf("All done in process %d.\n", getpid()); + cprintf("yield pass.\n"); + return 0; +} + diff --git a/code/lab6/Makefile b/code/lab6/Makefile new file mode 100644 index 0000000..e613b28 --- /dev/null +++ b/code/lab6/Makefile @@ -0,0 +1,323 @@ +PROJ := 6 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +GDB := $(GCCPREFIX)gdb + +CC ?= $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +USER_PREFIX := __user_ + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) +filename = $(basename $(notdir $(1))) +ubinfile = $(call outfile,$(addprefix $(USER_PREFIX),$(call filename,$(1)))) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# user programs + +UINCLUDE += user/include/ \ + user/libs/ + +USRCDIR += user + +ULIBDIR += user/libs + +UCFLAGS += $(addprefix -I,$(UINCLUDE)) +USER_BINS := + +$(call add_files_cc,$(call listf_cc,$(ULIBDIR)),ulibs,$(UCFLAGS)) +$(call add_files_cc,$(call listf_cc,$(USRCDIR)),uprog,$(UCFLAGS)) + +UOBJS := $(call read_packet,ulibs libs) + +define uprog_ld +__user_bin__ := $$(call ubinfile,$(1)) +USER_BINS += $$(__user_bin__) +$$(__user_bin__): tools/user.ld +$$(__user_bin__): $$(UOBJS) +$$(__user_bin__): $(1) | $$$$(dir $$$$@) + $(V)$(LD) $(LDFLAGS) -T tools/user.ld -o $$@ $$(UOBJS) $(1) + @$(OBJDUMP) -S $$@ > $$(call cgtype,$$<,o,asm) + @$(OBJDUMP) -t $$@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$$$/d' > $$(call cgtype,$$<,o,sym) +endef + +$(foreach p,$(call read_packet,uprog),$(eval $(call uprog_ld,$(p)))) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ \ + kern/fs/ \ + kern/process \ + kern/schedule \ + kern/syscall + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync \ + kern/fs \ + kern/process \ + kern/schedule \ + kern/syscall + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) $(USER_BINS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) -b binary $(USER_BINS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# ------------------------------------------------------------------- + +# create swap.img +SWAPIMG := $(call totarget,swap.img) + +$(SWAPIMG): + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + +$(call create_target,swap.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + run-.+ \ + build-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback + +.PHONY: qemu qemu-nox debug debug-nox +qemu: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +TERMINAL := gnome-terminal + +debug: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +RUN_PREFIX := _binary_$(OBJDIR)_$(USER_PREFIX) +MAKEOPTS := --quiet --no-print-directory + +run-%: build-% + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +build-%: touch + $(V)$(MAKE) $(MAKEOPTS) "DEFS+=-DTEST=$* -DTESTSTART=$(RUN_PREFIX)$*_out_start -DTESTSIZE=$(RUN_PREFIX)$*_out_size" + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := lab$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/process/proc.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab6/boot/asm.h b/code/lab6/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab6/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab6/boot/bootasm.S b/code/lab6/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab6/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab6/boot/bootmain.c b/code/lab6/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab6/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab6/kern/debug/assert.h b/code/lab6/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab6/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab6/kern/debug/kdebug.c b/code/lab6/kern/debug/kdebug.c new file mode 100644 index 0000000..fedbf5b --- /dev/null +++ b/code/lab6/kern/debug/kdebug.c @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* user STABS data structure */ +struct userstabdata { + const struct stab *stabs; + const struct stab *stab_end; + const char *stabstr; + const char *stabstr_end; +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + // find the relevant set of stabs + if (addr >= KERNBASE) { + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + } + else { + // user-program linker script, tools/user.ld puts the information about the + // program's stabs (included __STAB_BEGIN__, __STAB_END__, __STABSTR_BEGIN__, + // and __STABSTR_END__) in a structure located at virtual address USTAB. + const struct userstabdata *usd = (struct userstabdata *)USTAB; + + // make sure that debugger (current process) can access this memory + struct mm_struct *mm; + if (current == NULL || (mm = current->mm) == NULL) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)usd, sizeof(struct userstabdata), 0)) { + return -1; + } + + stabs = usd->stabs; + stab_end = usd->stab_end; + stabstr = usd->stabstr; + stabstr_end = usd->stabstr_end; + + // make sure the STABS and string table memory is valid + if (!user_mem_check(mm, (uintptr_t)stabs, (uintptr_t)stab_end - (uintptr_t)stabs, 0)) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)stabstr, stabstr_end - stabstr, 0)) { + return -1; + } + } + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab6/kern/debug/kdebug.h b/code/lab6/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab6/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab6/kern/debug/monitor.c b/code/lab6/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab6/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab6/kern/debug/monitor.h b/code/lab6/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab6/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab6/kern/debug/panic.c b/code/lab6/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab6/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab6/kern/debug/stab.h b/code/lab6/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab6/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab6/kern/driver/clock.c b/code/lab6/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab6/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab6/kern/driver/clock.h b/code/lab6/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab6/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab6/kern/driver/console.c b/code/lab6/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab6/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab6/kern/driver/console.h b/code/lab6/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab6/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab6/kern/driver/ide.c b/code/lab6/kern/driver/ide.c new file mode 100644 index 0000000..271cfa8 --- /dev/null +++ b/code/lab6/kern/driver/ide.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA_DATA 0x00 +#define ISA_ERROR 0x01 +#define ISA_PRECOMP 0x01 +#define ISA_CTRL 0x02 +#define ISA_SECCNT 0x02 +#define ISA_SECTOR 0x03 +#define ISA_CYL_LO 0x04 +#define ISA_CYL_HI 0x05 +#define ISA_SDH 0x06 +#define ISA_COMMAND 0x07 +#define ISA_STATUS 0x07 + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_DRQ 0x08 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_IDENTIFY 0xEC + +#define IDE_IDENT_SECTORS 20 +#define IDE_IDENT_MODEL 54 +#define IDE_IDENT_CAPABILITIES 98 +#define IDE_IDENT_CMDSETS 164 +#define IDE_IDENT_MAX_LBA 120 +#define IDE_IDENT_MAX_LBA_EXT 200 + +#define IO_BASE0 0x1F0 +#define IO_BASE1 0x170 +#define IO_CTRL0 0x3F4 +#define IO_CTRL1 0x374 + +#define MAX_IDE 4 +#define MAX_NSECS 128 +#define MAX_DISK_NSECS 0x10000000U +#define VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) + +static const struct { + unsigned short base; // I/O Base + unsigned short ctrl; // Control Base +} channels[2] = { + {IO_BASE0, IO_CTRL0}, + {IO_BASE1, IO_CTRL1}, +}; + +#define IO_BASE(ideno) (channels[(ideno) >> 1].base) +#define IO_CTRL(ideno) (channels[(ideno) >> 1].ctrl) + +static struct ide_device { + unsigned char valid; // 0 or 1 (If Device Really Exists) + unsigned int sets; // Commend Sets Supported + unsigned int size; // Size in Sectors + unsigned char model[41]; // Model in String +} ide_devices[MAX_IDE]; + +static int +ide_wait_ready(unsigned short iobase, bool check_error) { + int r; + while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY) + /* nothing */; + if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) { + return -1; + } + return 0; +} + +void +ide_init(void) { + static_assert((SECTSIZE % 4) == 0); + unsigned short ideno, iobase; + for (ideno = 0; ideno < MAX_IDE; ideno ++) { + /* assume that no device here */ + ide_devices[ideno].valid = 0; + + iobase = IO_BASE(ideno); + + /* wait device ready */ + ide_wait_ready(iobase, 0); + + /* step1: select drive */ + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4)); + ide_wait_ready(iobase, 0); + + /* step2: send ATA identify command */ + outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); + ide_wait_ready(iobase, 0); + + /* step3: polling */ + if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) { + continue ; + } + + /* device is ok */ + ide_devices[ideno].valid = 1; + + /* read identification space of the device */ + unsigned int buffer[128]; + insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); + + unsigned char *ident = (unsigned char *)buffer; + unsigned int sectors; + unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); + /* device use 48-bits or 28-bits addressing */ + if (cmdsets & (1 << 26)) { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); + } + else { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); + } + ide_devices[ideno].sets = cmdsets; + ide_devices[ideno].size = sectors; + + /* check if supports LBA */ + assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); + + unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; + unsigned int i, length = 40; + for (i = 0; i < length; i += 2) { + model[i] = data[i + 1], model[i + 1] = data[i]; + } + do { + model[i] = '\0'; + } while (i -- > 0 && model[i] == ' '); + + cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model); + } + + // enable ide interrupt + pic_enable(IRQ_IDE1); + pic_enable(IRQ_IDE2); +} + +bool +ide_device_valid(unsigned short ideno) { + return VALID_IDE(ideno); +} + +size_t +ide_device_size(unsigned short ideno) { + if (ide_device_valid(ideno)) { + return ide_devices[ideno].size; + } + return 0; +} + +int +ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_READ); + + int ret = 0; + for (; nsecs > 0; nsecs --, dst += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + insl(iobase, dst, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + +int +ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); + + int ret = 0; + for (; nsecs > 0; nsecs --, src += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + outsl(iobase, src, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + diff --git a/code/lab6/kern/driver/ide.h b/code/lab6/kern/driver/ide.h new file mode 100644 index 0000000..3e3fd21 --- /dev/null +++ b/code/lab6/kern/driver/ide.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_IDE_H__ +#define __KERN_DRIVER_IDE_H__ + +#include + +void ide_init(void); +bool ide_device_valid(unsigned short ideno); +size_t ide_device_size(unsigned short ideno); + +int ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs); +int ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs); + +#endif /* !__KERN_DRIVER_IDE_H__ */ + diff --git a/code/lab6/kern/driver/intr.c b/code/lab6/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab6/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab6/kern/driver/intr.h b/code/lab6/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab6/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab6/kern/driver/kbdreg.h b/code/lab6/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab6/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab6/kern/driver/picirq.c b/code/lab6/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab6/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab6/kern/driver/picirq.h b/code/lab6/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab6/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab6/kern/fs/fs.h b/code/lab6/kern/fs/fs.h new file mode 100644 index 0000000..92c05e7 --- /dev/null +++ b/code/lab6/kern/fs/fs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_FS_H__ +#define __KERN_FS_FS_H__ + +#include + +#define SECTSIZE 512 +#define PAGE_NSECT (PGSIZE / SECTSIZE) + +#define SWAP_DEV_NO 1 + +#endif /* !__KERN_FS_FS_H__ */ + diff --git a/code/lab6/kern/fs/swapfs.c b/code/lab6/kern/fs/swapfs.c new file mode 100644 index 0000000..d9f6090 --- /dev/null +++ b/code/lab6/kern/fs/swapfs.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include + +void +swapfs_init(void) { + static_assert((PGSIZE % SECTSIZE) == 0); + if (!ide_device_valid(SWAP_DEV_NO)) { + panic("swap fs isn't available.\n"); + } + max_swap_offset = ide_device_size(SWAP_DEV_NO) / (PGSIZE / SECTSIZE); +} + +int +swapfs_read(swap_entry_t entry, struct Page *page) { + return ide_read_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + +int +swapfs_write(swap_entry_t entry, struct Page *page) { + return ide_write_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + diff --git a/code/lab6/kern/fs/swapfs.h b/code/lab6/kern/fs/swapfs.h new file mode 100644 index 0000000..d433926 --- /dev/null +++ b/code/lab6/kern/fs/swapfs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_SWAPFS_H__ +#define __KERN_FS_SWAPFS_H__ + +#include +#include + +void swapfs_init(void); +int swapfs_read(swap_entry_t entry, struct Page *page); +int swapfs_write(swap_entry_t entry, struct Page *page); + +#endif /* !__KERN_FS_SWAPFS_H__ */ + diff --git a/code/lab6/kern/init/entry.S b/code/lab6/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab6/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab6/kern/init/init.c b/code/lab6/kern/init/init.c new file mode 100644 index 0000000..3341078 --- /dev/null +++ b/code/lab6/kern/init/init.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + vmm_init(); // init virtual memory management + sched_init(); // init scheduler + proc_init(); // init process table + + ide_init(); // init ide devices + swap_init(); // init swap + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + cpu_idle(); // run idle process +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab6/kern/libs/rb_tree.c b/code/lab6/kern/libs/rb_tree.c new file mode 100644 index 0000000..0a5fcc8 --- /dev/null +++ b/code/lab6/kern/libs/rb_tree.c @@ -0,0 +1,528 @@ +#include +#include +#include +#include +#include +#include + +/* rb_node_create - create a new rb_node */ +static inline rb_node * +rb_node_create(void) { + return kmalloc(sizeof(rb_node)); +} + +/* rb_tree_empty - tests if tree is empty */ +static inline bool +rb_tree_empty(rb_tree *tree) { + rb_node *nil = tree->nil, *root = tree->root; + return root->left == nil; +} + +/* * + * rb_tree_create - creates a new red-black tree, the 'compare' function + * is required and returns 'NULL' if failed. + * + * Note that, root->left should always point to the node that is the root + * of the tree. And nil points to a 'NULL' node which should always be + * black and may have arbitrary children and parent node. + * */ +rb_tree * +rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)) { + assert(compare != NULL); + + rb_tree *tree; + rb_node *nil, *root; + + if ((tree = kmalloc(sizeof(rb_tree))) == NULL) { + goto bad_tree; + } + + tree->compare = compare; + + if ((nil = rb_node_create()) == NULL) { + goto bad_node_cleanup_tree; + } + + nil->parent = nil->left = nil->right = nil; + nil->red = 0; + tree->nil = nil; + + if ((root = rb_node_create()) == NULL) { + goto bad_node_cleanup_nil; + } + + root->parent = root->left = root->right = nil; + root->red = 0; + tree->root = root; + return tree; + +bad_node_cleanup_nil: + kfree(nil); +bad_node_cleanup_tree: + kfree(tree); +bad_tree: + return NULL; +} + +/* * + * FUNC_ROTATE - rotates as described in "Introduction to Algorithm". + * + * For example, FUNC_ROTATE(rb_left_rotate, left, right) can be expaned to a + * left-rotate function, which requires an red-black 'tree' and a node 'x' + * to be rotated on. Basically, this function, named rb_left_rotate, makes the + * parent of 'x' be the left child of 'x', 'x' the parent of its parent before + * rotation and finally fixes other nodes accordingly. + * + * FUNC_ROTATE(xx, left, right) means left-rotate, + * and FUNC_ROTATE(xx, right, left) means right-rotate. + * */ +#define FUNC_ROTATE(func_name, _left, _right) \ +static void \ +func_name(rb_tree *tree, rb_node *x) { \ + rb_node *nil = tree->nil, *y = x->_right; \ + assert(x != tree->root && x != nil && y != nil); \ + x->_right = y->_left; \ + if (y->_left != nil) { \ + y->_left->parent = x; \ + } \ + y->parent = x->parent; \ + if (x == x->parent->_left) { \ + x->parent->_left = y; \ + } \ + else { \ + x->parent->_right = y; \ + } \ + y->_left = x; \ + x->parent = y; \ + assert(!(nil->red)); \ +} + +FUNC_ROTATE(rb_left_rotate, left, right); +FUNC_ROTATE(rb_right_rotate, right, left); + +#undef FUNC_ROTATE + +#define COMPARE(tree, node1, node2) \ + ((tree))->compare((node1), (node2)) + +/* * + * rb_insert_binary - insert @node to red-black @tree as if it were + * a regular binary tree. This function is only intended to be called + * by function rb_insert. + * */ +static inline void +rb_insert_binary(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node, *nil = tree->nil, *root = tree->root; + + z->left = z->right = nil; + y = root, x = y->left; + while (x != nil) { + y = x; + x = (COMPARE(tree, x, node) > 0) ? x->left : x->right; + } + z->parent = y; + if (y == root || COMPARE(tree, y, z) > 0) { + y->left = z; + } + else { + y->right = z; + } +} + +/* rb_insert - insert a node to red-black tree */ +void +rb_insert(rb_tree *tree, rb_node *node) { + rb_insert_binary(tree, node); + node->red = 1; + + rb_node *x = node, *y; + +#define RB_INSERT_SUB(_left, _right) \ + do { \ + y = x->parent->parent->_right; \ + if (y->red) { \ + x->parent->red = 0; \ + y->red = 0; \ + x->parent->parent->red = 1; \ + x = x->parent->parent; \ + } \ + else { \ + if (x == x->parent->_right) { \ + x = x->parent; \ + rb_##_left##_rotate(tree, x); \ + } \ + x->parent->red = 0; \ + x->parent->parent->red = 1; \ + rb_##_right##_rotate(tree, x->parent->parent); \ + } \ + } while (0) + + while (x->parent->red) { + if (x->parent == x->parent->parent->left) { + RB_INSERT_SUB(left, right); + } + else { + RB_INSERT_SUB(right, left); + } + } + tree->root->left->red = 0; + assert(!(tree->nil->red) && !(tree->root->red)); + +#undef RB_INSERT_SUB +} + +/* * + * rb_tree_successor - returns the successor of @node, or nil + * if no successor exists. Make sure that @node must belong to @tree, + * and this function should only be called by rb_node_prev. + * */ +static inline rb_node * +rb_tree_successor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->right) != nil) { + while (y->left != nil) { + y = y->left; + } + return y; + } + else { + y = x->parent; + while (x == y->right) { + x = y, y = y->parent; + } + if (y == tree->root) { + return nil; + } + return y; + } +} + +/* * + * rb_tree_predecessor - returns the predecessor of @node, or nil + * if no predecessor exists, likes rb_tree_successor. + * */ +static inline rb_node * +rb_tree_predecessor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->left) != nil) { + while (y->right != nil) { + y = y->right; + } + return y; + } + else { + y = x->parent; + while (x == y->left) { + if (y == tree->root) { + return nil; + } + x = y, y = y->parent; + } + return y; + } +} + +/* * + * rb_search - returns a node with value 'equal' to @key (according to + * function @compare). If there're multiple nodes with value 'equal' to @key, + * the functions returns the one highest in the tree. + * */ +rb_node * +rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key) { + rb_node *nil = tree->nil, *node = tree->root->left; + int r; + while (node != nil && (r = compare(node, key)) != 0) { + node = (r > 0) ? node->left : node->right; + } + return (node != nil) ? node : NULL; +} + +/* * + * rb_delete_fixup - performs rotations and changes colors to restore + * red-black properties after a node is deleted. + * */ +static void +rb_delete_fixup(rb_tree *tree, rb_node *node) { + rb_node *x = node, *w, *root = tree->root->left; + +#define RB_DELETE_FIXUP_SUB(_left, _right) \ + do { \ + w = x->parent->_right; \ + if (w->red) { \ + w->red = 0; \ + x->parent->red = 1; \ + rb_##_left##_rotate(tree, x->parent); \ + w = x->parent->_right; \ + } \ + if (!w->_left->red && !w->_right->red) { \ + w->red = 1; \ + x = x->parent; \ + } \ + else { \ + if (!w->_right->red) { \ + w->_left->red = 0; \ + w->red = 1; \ + rb_##_right##_rotate(tree, w); \ + w = x->parent->_right; \ + } \ + w->red = x->parent->red; \ + x->parent->red = 0; \ + w->_right->red = 0; \ + rb_##_left##_rotate(tree, x->parent); \ + x = root; \ + } \ + } while (0) + + while (x != root && !x->red) { + if (x == x->parent->left) { + RB_DELETE_FIXUP_SUB(left, right); + } + else { + RB_DELETE_FIXUP_SUB(right, left); + } + } + x->red = 0; + +#undef RB_DELETE_FIXUP_SUB +} + +/* * + * rb_delete - deletes @node from @tree, and calls rb_delete_fixup to + * restore red-black properties. + * */ +void +rb_delete(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node; + rb_node *nil = tree->nil, *root = tree->root; + + y = (z->left == nil || z->right == nil) ? z : rb_tree_successor(tree, z); + x = (y->left != nil) ? y->left : y->right; + + assert(y != root && y != nil); + + x->parent = y->parent; + if (y == y->parent->left) { + y->parent->left = x; + } + else { + y->parent->right = x; + } + + bool need_fixup = !(y->red); + + if (y != z) { + if (z == z->parent->left) { + z->parent->left = y; + } + else { + z->parent->right = y; + } + z->left->parent = z->right->parent = y; + *y = *z; + } + if (need_fixup) { + rb_delete_fixup(tree, x); + } +} + +/* rb_tree_destroy - destroy a tree and free memory */ +void +rb_tree_destroy(rb_tree *tree) { + kfree(tree->root); + kfree(tree->nil); + kfree(tree); +} + +/* * + * rb_node_prev - returns the predecessor node of @node in @tree, + * or 'NULL' if no predecessor exists. + * */ +rb_node * +rb_node_prev(rb_tree *tree, rb_node *node) { + rb_node *prev = rb_tree_predecessor(tree, node); + return (prev != tree->nil) ? prev : NULL; +} + +/* * + * rb_node_next - returns the successor node of @node in @tree, + * or 'NULL' if no successor exists. + * */ +rb_node * +rb_node_next(rb_tree *tree, rb_node *node) { + rb_node *next = rb_tree_successor(tree, node); + return (next != tree->nil) ? next : NULL; +} + +/* rb_node_root - returns the root node of a @tree, or 'NULL' if tree is empty */ +rb_node * +rb_node_root(rb_tree *tree) { + rb_node *node = tree->root->left; + return (node != tree->nil) ? node : NULL; +} + +/* rb_node_left - gets the left child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_left(rb_tree *tree, rb_node *node) { + rb_node *left = node->left; + return (left != tree->nil) ? left : NULL; +} + +/* rb_node_right - gets the right child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_right(rb_tree *tree, rb_node *node) { + rb_node *right = node->right; + return (right != tree->nil) ? right : NULL; +} + +int +check_tree(rb_tree *tree, rb_node *node) { + rb_node *nil = tree->nil; + if (node == nil) { + assert(!node->red); + return 1; + } + if (node->left != nil) { + assert(COMPARE(tree, node, node->left) >= 0); + assert(node->left->parent == node); + } + if (node->right != nil) { + assert(COMPARE(tree, node, node->right) <= 0); + assert(node->right->parent == node); + } + if (node->red) { + assert(!node->left->red && !node->right->red); + } + int hb_left = check_tree(tree, node->left); + int hb_right = check_tree(tree, node->right); + assert(hb_left == hb_right); + int hb = hb_left; + if (!node->red) { + hb ++; + } + return hb; +} + +static void * +check_safe_kmalloc(size_t size) { + void *ret = kmalloc(size); + assert(ret != NULL); + return ret; +} + +struct check_data { + long data; + rb_node rb_link; +}; + +#define rbn2data(node) \ + (to_struct(node, struct check_data, rb_link)) + +static inline int +check_compare1(rb_node *node1, rb_node *node2) { + return rbn2data(node1)->data - rbn2data(node2)->data; +} + +static inline int +check_compare2(rb_node *node, void *key) { + return rbn2data(node)->data - (long)key; +} + +void +check_rb_tree(void) { + rb_tree *tree = rb_tree_create(check_compare1); + assert(tree != NULL); + + rb_node *nil = tree->nil, *root = tree->root; + assert(!nil->red && root->left == nil); + + int total = 1000; + struct check_data **all = check_safe_kmalloc(sizeof(struct check_data *) * total); + + long i; + for (i = 0; i < total; i ++) { + all[i] = check_safe_kmalloc(sizeof(struct check_data)); + all[i]->data = i; + } + + int *mark = check_safe_kmalloc(sizeof(int) * total); + memset(mark, 0, sizeof(int) * total); + + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + int j = (rand() % (total - i)) + i; + struct check_data *z = all[i]; + all[i] = all[j]; + all[j] = z; + } + + memset(mark, 0, sizeof(int) * total); + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_node *node; + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)(all[i]->data)); + assert(node != NULL && node == &(all[i]->rb_link)); + } + + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)i); + assert(node != NULL && rbn2data(node)->data == i); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(!nil->red && root->left == nil); + + long max = 32; + if (max > total) { + max = total; + } + + for (i = 0; i < max; i ++) { + all[i]->data = max; + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + for (i = 0; i < max; i ++) { + node = rb_search(tree, check_compare2, (void *)max); + assert(node != NULL && rbn2data(node)->data == max); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(rb_tree_empty(tree)); + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_tree_destroy(tree); + + for (i = 0; i < total; i ++) { + kfree(all[i]); + } + + kfree(mark); + kfree(all); +} + diff --git a/code/lab6/kern/libs/rb_tree.h b/code/lab6/kern/libs/rb_tree.h new file mode 100644 index 0000000..a2aa9aa --- /dev/null +++ b/code/lab6/kern/libs/rb_tree.h @@ -0,0 +1,32 @@ +#ifndef __KERN_LIBS_RB_TREE_H__ +#define __KERN_LIBS_RB_TREE_H__ + +#include + +typedef struct rb_node { + bool red; // if red = 0, it's a black node + struct rb_node *parent; + struct rb_node *left, *right; +} rb_node; + +typedef struct rb_tree { + // compare function should return -1 if *node1 < *node2, 1 if *node1 > *node2, and 0 otherwise + int (*compare)(rb_node *node1, rb_node *node2); + struct rb_node *nil, *root; +} rb_tree; + +rb_tree *rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)); +void rb_tree_destroy(rb_tree *tree); +void rb_insert(rb_tree *tree, rb_node *node); +void rb_delete(rb_tree *tree, rb_node *node); +rb_node *rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key); +rb_node *rb_node_prev(rb_tree *tree, rb_node *node); +rb_node *rb_node_next(rb_tree *tree, rb_node *node); +rb_node *rb_node_root(rb_tree *tree); +rb_node *rb_node_left(rb_tree *tree, rb_node *node); +rb_node *rb_node_right(rb_tree *tree, rb_node *node); + +void check_rb_tree(void); + +#endif /* !__KERN_LIBS_RBTREE_H__ */ + diff --git a/code/lab6/kern/libs/readline.c b/code/lab6/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab6/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab6/kern/libs/stdio.c b/code/lab6/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab6/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab6/kern/mm/default_pmm.c b/code/lab6/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab6/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab6/kern/mm/default_pmm.h b/code/lab6/kern/mm/default_pmm.h new file mode 100644 index 0000000..5f4e6fc --- /dev/null +++ b/code/lab6/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; +extern free_area_t free_area; +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab6/kern/mm/kmalloc.c b/code/lab6/kern/mm/kmalloc.c new file mode 100644 index 0000000..aa5bb90 --- /dev/null +++ b/code/lab6/kern/mm/kmalloc.c @@ -0,0 +1,640 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The slab allocator used in ucore is based on an algorithm first introduced by + Jeff Bonwick for the SunOS operating system. The paper can be download from + http://citeseer.ist.psu.edu/bonwick94slab.html + An implementation of the Slab Allocator as described in outline in; + UNIX Internals: The New Frontiers by Uresh Vahalia + Pub: Prentice Hall ISBN 0-13-101908-2 + Within a kernel, a considerable amount of memory is allocated for a finite set + of objects such as file descriptors and other common structures. Jeff found that + the amount of time required to initialize a regular object in the kernel exceeded + the amount of time required to allocate and deallocate it. His conclusion was + that instead of freeing the memory back to a global pool, he would have the memory + remain initialized for its intended purpose. + In our simple slab implementation, the the high-level organization of the slab + structures is simplied. At the highest level is an array slab_cache[SLAB_CACHE_NUM], + and each array element is a slab_cache which has slab chains. Each slab_cache has + two list, one list chains the full allocated slab, and another list chains the notfull + allocated(maybe empty) slab. And each slab has fixed number(2^n) of pages. In each + slab, there are a lot of objects (such as ) with same fixed size(32B ~ 128KB). + + +----------------------------------+ + | slab_cache[0] for 0~32B obj | + +----------------------------------+ + | slab_cache[1] for 33B~64B obj |-->lists for slabs + +----------------------------------+ | + | slab_cache[2] for 65B~128B obj | | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + +----------------------------------+ | + | slab_cache[12]for 64KB~128KB obj | | + +----------------------------------+ | + | + slabs_full/slabs_not +---------------------+ + -<-----------<----------<-+ + | | | + slab1 slab2 slab3... + | + |-------|-------| + pages1 pages2 pages3... + | + | + | + slab_t+n*bufctl_t+obj1-obj2-obj3...objn (the size of obj is small) + | + OR + | + obj1-obj2-obj3...objn WITH slab_t+n*bufctl_t in another slab (the size of obj is BIG) + + The important functions are: + kmem_cache_grow(kmem_cache_t *cachep) + kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) + kmalloc(size_t size): used by outside functions need dynamicly get memory + kfree(void *objp): used by outside functions need dynamicly release memory +*/ + +#define BUFCTL_END 0xFFFFFFFFL // the signature of the last bufctl +#define SLAB_LIMIT 0xFFFFFFFEL // the max value of obj number + +typedef size_t kmem_bufctl_t; //the index of obj in slab + +typedef struct slab_s { + list_entry_t slab_link; // the list entry linked to kmem_cache list + void *s_mem; // the kernel virtual address of the first obj in slab + size_t inuse; // the number of allocated objs + size_t offset; // the first obj's offset value in slab + kmem_bufctl_t free; // the first free obj's index in slab +} slab_t; + +// get the slab address according to the link element (see list.h) +#define le2slab(le, member) \ + to_struct((le), slab_t, member) + +typedef struct kmem_cache_s kmem_cache_t; + + +struct kmem_cache_s { + list_entry_t slabs_full; // list for fully allocated slabs + list_entry_t slabs_notfull; // list for not-fully allocated slabs + + size_t objsize; // the fixed size of obj + size_t num; // number of objs per slab + size_t offset; // this first obj's offset in slab + bool off_slab; // the control part of slab in slab or not. + + /* order of pages per slab (2^n) */ + size_t page_order; + + kmem_cache_t *slab_cachep; +}; + +#define MIN_SIZE_ORDER 5 // 32 +#define MAX_SIZE_ORDER 17 // 128k +#define SLAB_CACHE_NUM (MAX_SIZE_ORDER - MIN_SIZE_ORDER + 1) + +static kmem_cache_t slab_cache[SLAB_CACHE_NUM]; + +static void init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align); +static void check_slab(void); + + +//slab_init - call init_kmem_cache function to reset the slab_cache array +static void +slab_init(void) { + size_t i; + //the align bit for obj in slab. 2^n could be better for performance + size_t align = 16; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + init_kmem_cache(slab_cache + i, 1 << (i + MIN_SIZE_ORDER), align); + } + check_slab(); +} + +inline void +kmalloc_init(void) { + slab_init(); + cprintf("kmalloc_init() succeeded!\n"); +} + +//slab_allocated - summary the total size of allocated objs +static size_t +slab_allocated(void) { + size_t total = 0; + int i; + bool intr_flag; + local_intr_save(intr_flag); + { + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + list_entry_t *list, *le; + list = le = &(cachep->slabs_full); + while ((le = list_next(le)) != list) { + total += cachep->num * cachep->objsize; + } + list = le = &(cachep->slabs_notfull); + while ((le = list_next(le)) != list) { + slab_t *slabp = le2slab(le, slab_link); + total += slabp->inuse * cachep->objsize; + } + } + } + local_intr_restore(intr_flag); + return total; +} + +inline size_t +kallocated(void) { + return slab_allocated(); +} + +// slab_mgmt_size - get the size of slab control area (slab_t+num*kmem_bufctl_t) +static size_t +slab_mgmt_size(size_t num, size_t align) { + return ROUNDUP(sizeof(slab_t) + num * sizeof(kmem_bufctl_t), align); +} + +// cacahe_estimate - estimate the number of objs in a slab +static void +cache_estimate(size_t order, size_t objsize, size_t align, bool off_slab, size_t *remainder, size_t *num) { + size_t nr_objs, mgmt_size; + size_t slab_size = (PGSIZE << order); + + if (off_slab) { + mgmt_size = 0; + nr_objs = slab_size / objsize; + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + } + else { + nr_objs = (slab_size - sizeof(slab_t)) / (objsize + sizeof(kmem_bufctl_t)); + while (slab_mgmt_size(nr_objs, align) + nr_objs * objsize > slab_size) { + nr_objs --; + } + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + mgmt_size = slab_mgmt_size(nr_objs, align); + } + *num = nr_objs; + *remainder = slab_size - nr_objs * objsize - mgmt_size; +} + +// calculate_slab_order - estimate the size(4K~4M) of slab +// paramemters: +// cachep: the slab_cache +// objsize: the size of obj +// align: align bit for objs +// off_slab: the control part of slab in slab or not +// left_over: the size of can not be used area in slab +static void +calculate_slab_order(kmem_cache_t *cachep, size_t objsize, size_t align, bool off_slab, size_t *left_over) { + size_t order; + for (order = 0; order <= KMALLOC_MAX_ORDER; order ++) { + size_t num, remainder; + cache_estimate(order, objsize, align, off_slab, &remainder, &num); + if (num != 0) { + if (off_slab) { + size_t off_slab_limit = objsize - sizeof(slab_t); + off_slab_limit /= sizeof(kmem_bufctl_t); + if (num > off_slab_limit) { + panic("off_slab: objsize = %d, num = %d.", objsize, num); + } + } + if (remainder * 8 <= (PGSIZE << order)) { + cachep->num = num; + cachep->page_order = order; + if (left_over != NULL) { + *left_over = remainder; + } + return ; + } + } + } + panic("calculate_slab_over: failed."); +} + +// getorder - find order, should satisfy n <= minest 2^order +static inline size_t +getorder(size_t n) { + size_t order = MIN_SIZE_ORDER, order_size = (1 << order); + for (; order <= MAX_SIZE_ORDER; order ++, order_size <<= 1) { + if (n <= order_size) { + return order; + } + } + panic("getorder failed. %d\n", n); +} + +// init_kmem_cache - initial a slab_cache cachep according to the obj with the size = objsize +static void +init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align) { + list_init(&(cachep->slabs_full)); + list_init(&(cachep->slabs_notfull)); + + objsize = ROUNDUP(objsize, align); + cachep->objsize = objsize; + cachep->off_slab = (objsize >= (PGSIZE >> 3)); + + size_t left_over; + calculate_slab_order(cachep, objsize, align, cachep->off_slab, &left_over); + + assert(cachep->num > 0); + + size_t mgmt_size = slab_mgmt_size(cachep->num, align); + + if (cachep->off_slab && left_over >= mgmt_size) { + cachep->off_slab = 0; + } + + if (cachep->off_slab) { + cachep->offset = 0; + cachep->slab_cachep = slab_cache + (getorder(mgmt_size) - MIN_SIZE_ORDER); + } + else { + cachep->offset = mgmt_size; + } +} + +static void *kmem_cache_alloc(kmem_cache_t *cachep); + +#define slab_bufctl(slabp) \ + ((kmem_bufctl_t*)(((slab_t *)(slabp)) + 1)) + +// kmem_cache_slabmgmt - get the address of a slab according to page +// - and initialize the slab according to cachep +static slab_t * +kmem_cache_slabmgmt(kmem_cache_t *cachep, struct Page *page) { + void *objp = page2kva(page); + slab_t *slabp; + if (cachep->off_slab) { + if ((slabp = kmem_cache_alloc(cachep->slab_cachep)) == NULL) { + return NULL; + } + } + else { + slabp = page2kva(page); + } + slabp->inuse = 0; + slabp->offset = cachep->offset; + slabp->s_mem = objp + cachep->offset; + return slabp; +} + +#define SET_PAGE_CACHE(page, cachep) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + kmem_cache_t **__cachepp = (kmem_cache_t **)&(__page->page_link.next); \ + *__cachepp = (kmem_cache_t *)(cachep); \ + } while (0) + +#define SET_PAGE_SLAB(page, slabp) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + slab_t **__cachepp = (slab_t **)&(__page->page_link.prev); \ + *__cachepp = (slab_t *)(slabp); \ + } while (0) + +// kmem_cache_grow - allocate a new slab by calling alloc_pages +// - set control area in the new slab +static bool +kmem_cache_grow(kmem_cache_t *cachep) { + struct Page *page = alloc_pages(1 << cachep->page_order); + if (page == NULL) { + goto failed; + } + + slab_t *slabp; + if ((slabp = kmem_cache_slabmgmt(cachep, page)) == NULL) { + goto oops; + } + + size_t order_size = (1 << cachep->page_order); + do { + //setup this page in the free list (see memlayout.h: struct page)??? + SET_PAGE_CACHE(page, cachep); + SET_PAGE_SLAB(page, slabp); + //this page is used for slab + SetPageSlab(page); + page ++; + } while (-- order_size); + + int i; + for (i = 0; i < cachep->num; i ++) { + slab_bufctl(slabp)[i] = i + 1; + } + slab_bufctl(slabp)[cachep->num - 1] = BUFCTL_END; + slabp->free = 0; + + bool intr_flag; + local_intr_save(intr_flag); + { + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } + local_intr_restore(intr_flag); + return 1; + +oops: + free_pages(page, 1 << cachep->page_order); +failed: + return 0; +} + +// kmem_cache_alloc_one - allocate a obj in a slab +static void * +kmem_cache_alloc_one(kmem_cache_t *cachep, slab_t *slabp) { + slabp->inuse ++; + void *objp = slabp->s_mem + slabp->free * cachep->objsize; + slabp->free = slab_bufctl(slabp)[slabp->free]; + + if (slabp->free == BUFCTL_END) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_full), &(slabp->slab_link)); + } + return objp; +} + +// kmem_cache_alloc - call kmem_cache_alloc_one function to allocate a obj +// - if no free obj, try to allocate a slab +static void * +kmem_cache_alloc(kmem_cache_t *cachep) { + void *objp; + bool intr_flag; + +try_again: + local_intr_save(intr_flag); + if (list_empty(&(cachep->slabs_notfull))) { + goto alloc_new_slab; + } + slab_t *slabp = le2slab(list_next(&(cachep->slabs_notfull)), slab_link); + objp = kmem_cache_alloc_one(cachep, slabp); + local_intr_restore(intr_flag); + return objp; + +alloc_new_slab: + local_intr_restore(intr_flag); + + if (kmem_cache_grow(cachep)) { + goto try_again; + } + return NULL; +} + +// kmalloc - simple interface used by outside functions +// - to allocate a free memory using kmem_cache_alloc function +void * +kmalloc(size_t size) { + assert(size > 0); + size_t order = getorder(size); + if (order > MAX_SIZE_ORDER) { + return NULL; + } + return kmem_cache_alloc(slab_cache + (order - MIN_SIZE_ORDER)); +} + +static void kmem_cache_free(kmem_cache_t *cachep, void *obj); + +// kmem_slab_destroy - call free_pages & kmem_cache_free to free a slab +static void +kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) { + struct Page *page = kva2page(slabp->s_mem - slabp->offset); + + struct Page *p = page; + size_t order_size = (1 << cachep->page_order); + do { + assert(PageSlab(p)); + ClearPageSlab(p); + p ++; + } while (-- order_size); + + free_pages(page, 1 << cachep->page_order); + + if (cachep->off_slab) { + kmem_cache_free(cachep->slab_cachep, slabp); + } +} + +// kmem_cache_free_one - free an obj in a slab +// - if slab->inuse==0, then free the slab +static void +kmem_cache_free_one(kmem_cache_t *cachep, slab_t *slabp, void *objp) { + //should not use divide operator ??? + size_t objnr = (objp - slabp->s_mem) / cachep->objsize; + slab_bufctl(slabp)[objnr] = slabp->free; + slabp->free = objnr; + + slabp->inuse --; + + if (slabp->inuse == 0) { + list_del(&(slabp->slab_link)); + kmem_slab_destroy(cachep, slabp); + } + else if (slabp->inuse == cachep->num -1 ) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } +} + +#define GET_PAGE_CACHE(page) \ + (kmem_cache_t *)((page)->page_link.next) + +#define GET_PAGE_SLAB(page) \ + (slab_t *)((page)->page_link.prev) + +// kmem_cache_free - call kmem_cache_free_one function to free an obj +static void +kmem_cache_free(kmem_cache_t *cachep, void *objp) { + bool intr_flag; + struct Page *page = kva2page(objp); + + if (!PageSlab(page)) { + panic("not a slab page %08x\n", objp); + } + local_intr_save(intr_flag); + { + kmem_cache_free_one(cachep, GET_PAGE_SLAB(page), objp); + } + local_intr_restore(intr_flag); +} + +// kfree - simple interface used by ooutside functions to free an obj +void +kfree(void *objp) { + kmem_cache_free(GET_PAGE_CACHE(kva2page(objp)), objp); +} + +static inline void +check_slab_empty(void) { + int i; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + assert(list_empty(&(cachep->slabs_full))); + assert(list_empty(&(cachep->slabs_notfull))); + } +} + +void +check_slab(void) { + int i; + void *v0, *v1; + + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = slab_allocated(); + + /* slab must be empty now */ + check_slab_empty(); + assert(slab_allocated() == 0); + + kmem_cache_t *cachep0, *cachep1; + + cachep0 = slab_cache; + assert(cachep0->objsize == 32 && cachep0->num > 1 && !cachep0->off_slab); + assert((v0 = kmalloc(16)) != NULL); + + slab_t *slabp0, *slabp1; + + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + assert(slabp0->inuse == 1 && list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + struct Page *p0, *p1; + size_t order_size; + + + p0 = kva2page(slabp0->s_mem - slabp0->offset), p1 = p0; + order_size = (1 << cachep0->page_order); + for (i = 0; i < cachep0->page_order; i ++, p1 ++) { + assert(PageSlab(p1)); + assert(GET_PAGE_CACHE(p1) == cachep0 && GET_PAGE_SLAB(p1) == slabp0); + } + + assert(v0 == slabp0->s_mem); + assert((v1 = kmalloc(16)) != NULL && v1 == v0 + 32); + + kfree(v0); + assert(slabp0->free == 0); + kfree(v1); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->page_order; i ++, p0 ++) { + assert(!PageSlab(p0)); + } + + + v0 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + for (i = 0; i < cachep0->num - 1; i ++) { + kmalloc(16); + } + + assert(slabp0->inuse == cachep0->num); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + v1 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + kfree(v0); + assert(list_empty(&(cachep0->slabs_full))); + assert(list_next(&(slabp0->slab_link)) == &(slabp1->slab_link) + || list_next(&(slabp1->slab_link)) == &(slabp0->slab_link)); + + kfree(v1); + assert(!list_empty(&(cachep0->slabs_notfull))); + assert(list_next(&(cachep0->slabs_notfull)) == &(slabp0->slab_link)); + assert(list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + v1 = kmalloc(16); + assert(v1 == v0); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->num; i ++) { + kfree(v1 + i * cachep0->objsize); + } + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + cachep0 = slab_cache; + + bool has_off_slab = 0; + for (i = 0; i < SLAB_CACHE_NUM; i ++, cachep0 ++) { + if (cachep0->off_slab) { + has_off_slab = 1; + cachep1 = cachep0->slab_cachep; + if (!cachep1->off_slab) { + break; + } + } + } + + if (!has_off_slab) { + goto check_pass; + } + + assert(cachep0->off_slab && !cachep1->off_slab); + assert(cachep1 < cachep0); + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + assert(list_empty(&(cachep1->slabs_full))); + assert(list_empty(&(cachep1->slabs_notfull))); + + v0 = kmalloc(cachep0->objsize); + p0 = kva2page(v0); + assert(page2kva(p0) == v0); + + if (cachep0->num == 1) { + assert(!list_empty(&(cachep0->slabs_full))); + slabp0 = le2slab(list_next(&(cachep0->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + } + + assert(slabp0 != NULL); + + if (cachep1->num == 1) { + assert(!list_empty(&(cachep1->slabs_full))); + slabp1 = le2slab(list_next(&(cachep1->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep1->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep1->slabs_notfull)), slab_link); + } + + assert(slabp1 != NULL); + + order_size = (1 << cachep0->page_order); + for (i = 0; i < order_size; i ++, p0 ++) { + assert(PageSlab(p0)); + assert(GET_PAGE_CACHE(p0) == cachep0 && GET_PAGE_SLAB(p0) == slabp0); + } + + kfree(v0); + +check_pass: + + check_rb_tree(); + check_slab_empty(); + assert(slab_allocated() == 0); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == slab_allocated()); + + cprintf("check_slab() succeeded!\n"); +} + diff --git a/code/lab6/kern/mm/kmalloc.h b/code/lab6/kern/mm/kmalloc.h new file mode 100644 index 0000000..8617975 --- /dev/null +++ b/code/lab6/kern/mm/kmalloc.h @@ -0,0 +1,16 @@ +#ifndef __KERN_MM_SLAB_H__ +#define __KERN_MM_SLAB_H__ + +#include + +#define KMALLOC_MAX_ORDER 10 + +void kmalloc_init(void); + +void *kmalloc(size_t n); +void kfree(void *objp); + +size_t kallocated(void); + +#endif /* !__KERN_MM_SLAB_H__ */ + diff --git a/code/lab6/kern/mm/memlayout.h b/code/lab6/kern/mm/memlayout.h new file mode 100644 index 0000000..d84bf93 --- /dev/null +++ b/code/lab6/kern/mm/memlayout.h @@ -0,0 +1,169 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | Invalid Memory (*) | --/-- + * USERTOP -------------> +---------------------------------+ 0xB0000000 + * | User stack | + * +---------------------------------+ + * | | + * : : + * | ~~~~~~~~~~~~~~~~ | + * : : + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | User Program & Heap | + * UTEXT ---------------> +---------------------------------+ 0x00800000 + * | Invalid Memory (*) | --/-- + * | - - - - - - - - - - - - - - - | + * | User STAB Data (optional) | + * USERBASE, USTAB------> +---------------------------------+ 0x00200000 + * | Invalid Memory (*) | --/-- + * 0 -------------------> +---------------------------------+ 0x00000000 + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#define USERTOP 0xB0000000 +#define USTACKTOP USERTOP +#define USTACKPAGE 256 // # of pages in user stack +#define USTACKSIZE (USTACKPAGE * PGSIZE) // sizeof user stack + +#define USERBASE 0x00200000 +#define UTEXT 0x00800000 // where user programs generally begin +#define USTAB USERBASE // the location of the user STABS data structure + +#define USER_ACCESS(start, end) \ +(USERBASE <= (start) && (start) < (end) && (end) <= USERTOP) + +#define KERN_ACCESS(start, end) \ +(KERNBASE <= (start) && (start) < (end) && (end) <= KERNTOP) + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; +typedef pte_t swap_entry_t; //the pte can also be a swap entry + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // used in buddy system, stores the order (the X in 2^X) of the continuous memory block + int zone_num; // used in buddy system, the No. of zone which the page belongs to + list_entry_t page_link; // free list link + list_entry_t pra_page_link; // used for pra (page replace algorithm) + uintptr_t pra_vaddr; // used for pra (page replace algorithm) +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +/* for slab style kmalloc */ +#define PG_slab 2 // page frame is included in a slab +#define SetPageSlab(page) set_bit(PG_slab, &((page)->flags)) +#define ClearPageSlab(page) clear_bit(PG_slab, &((page)->flags)) +#define PageSlab(page) test_bit(PG_slab, &((page)->flags)) + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab6/kern/mm/mmu.h b/code/lab6/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab6/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab6/kern/mm/pmm.c b/code/lab6/kern/mm/pmm.c new file mode 100644 index 0000000..cc3f28c --- /dev/null +++ b/code/lab6/kern/mm/pmm.c @@ -0,0 +1,759 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + + while (1) + { + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + + if (page != NULL || n > 1 || swap_init_ok == 0) break; + + extern struct mm_struct *check_mm_struct; + //cprintf("page %x, call swap_out in alloc_pages %d\n",page, n); + swap_out(check_mm_struct, n, 0); + } + //cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages)); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + + kmalloc_init(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +void +unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + do { + pte_t *ptep = get_pte(pgdir, start, 0); + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + if (*ptep != 0) { + page_remove_pte(pgdir, start, ptep); + } + start += PGSIZE; + } while (start != 0 && start < end); +} + +void +exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + start = ROUNDDOWN(start, PTSIZE); + do { + int pde_idx = PDX(start); + if (pgdir[pde_idx] & PTE_P) { + free_page(pde2page(pgdir[pde_idx])); + pgdir[pde_idx] = 0; + } + start += PTSIZE; + } while (start != 0 && start < end); +} +/* copy_range - copy content of memory (start, end) of one process A to another process B + * @to: the addr of process B's Page Directory + * @from: the addr of process A's Page Directory + * @share: flags to indicate to dup OR share. We just use dup method, so it didn't be used. + * + * CALL GRAPH: copy_mm-->dup_mmap-->copy_range + */ +int +copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + // copy content by page unit. + do { + //call get_pte to find process A's pte according to the addr start + pte_t *ptep = get_pte(from, start, 0), *nptep; + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + //call get_pte to find process B's pte according to the addr start. If pte is NULL, just alloc a PT + if (*ptep & PTE_P) { + if ((nptep = get_pte(to, start, 1)) == NULL) { + return -E_NO_MEM; + } + uint32_t perm = (*ptep & PTE_USER); + //get page from ptep + struct Page *page = pte2page(*ptep); + // alloc a page for process B + struct Page *npage=alloc_page(); + assert(page!=NULL); + assert(npage!=NULL); + int ret=0; + /* LAB5:EXERCISE2 YOUR CODE + * replicate content of page to npage, build the map of phy addr of nage with the linear addr start + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h) + * page_insert: build the map of phy addr of an Page with the linear addr la + * memcpy: typical memory copy function + * + * (1) find src_kvaddr: the kernel virtual address of page + * (2) find dst_kvaddr: the kernel virtual address of npage + * (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE + * (4) build the map of phy addr of nage with the linear addr start + */ + assert(ret == 0); + } + start += PGSIZE; + } while (start != 0 && start < end); + return 0; +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +// pgdir_alloc_page - call alloc_page & page_insert functions to +// - allocate a page size memory & setup an addr map +// - pa<->la with linear address la and the PDT pgdir +struct Page * +pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { + struct Page *page = alloc_page(); + if (page != NULL) { + if (page_insert(pgdir, page, la, perm) != 0) { + free_page(page); + return NULL; + } + if (swap_init_ok){ + if(check_mm_struct!=NULL) { + swap_map_swappable(check_mm_struct, la, page, 0); + page->pra_vaddr=la; + assert(page_ref(page) == 1); + //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); + } + else { //now current is existed, should fix it in the future + //swap_map_swappable(current->mm, la, page, 0); + //page->pra_vaddr=la; + //assert(page_ref(page) == 1); + //panic("pgdir_alloc_page: no pages. now current is existed, should fix it in the future\n"); + } + } + + } + + return page; +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} diff --git a/code/lab6/kern/mm/pmm.h b/code/lab6/kern/mm/pmm.h new file mode 100644 index 0000000..1e229a7 --- /dev/null +++ b/code/lab6/kern/mm/pmm.h @@ -0,0 +1,145 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); +struct Page *pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm); +void unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +void exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab6/kern/mm/swap.c b/code/lab6/kern/mm/swap.c new file mode 100644 index 0000000..281889d --- /dev/null +++ b/code/lab6/kern/mm/swap.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1 +#define CHECK_VALID_VIR_PAGE_NUM 5 +#define BEING_CHECK_VALID_VADDR 0X1000 +#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000 +// the max number of valid physical page for check +#define CHECK_VALID_PHY_PAGE_NUM 4 +// the max access seq number +#define MAX_SEQ_NO 10 + +static struct swap_manager *sm; +size_t max_swap_offset; + +volatile int swap_init_ok = 0; + +unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM]; + +unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO]; + +static void check_swap(void); + +int +swap_init(void) +{ + swapfs_init(); + + if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) + { + panic("bad max_swap_offset %08x.\n", max_swap_offset); + } + + + sm = &swap_manager_fifo; + int r = sm->init(); + + if (r == 0) + { + swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + check_swap(); + } + + return r; +} + +int +swap_init_mm(struct mm_struct *mm) +{ + return sm->init_mm(mm); +} + +int +swap_tick_event(struct mm_struct *mm) +{ + return sm->tick_event(mm); +} + +int +swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + return sm->map_swappable(mm, addr, page, swap_in); +} + +int +swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return sm->set_unswappable(mm, addr); +} + +volatile unsigned int swap_out_num=0; + +int +swap_out(struct mm_struct *mm, int n, int in_tick) +{ + int i; + for (i = 0; i != n; ++ i) + { + uintptr_t v; + //struct Page **ptr_page=NULL; + struct Page *page; + // cprintf("i %d, SWAP: call swap_out_victim\n",i); + int r = sm->swap_out_victim(mm, &page, in_tick); + if (r != 0) { + cprintf("i %d, swap_out: call swap_out_victim failed\n",i); + break; + } + //assert(!PageReserved(page)); + + //cprintf("SWAP: choose victim page 0x%08x\n", page); + + v=page->pra_vaddr; + pte_t *ptep = get_pte(mm->pgdir, v, 0); + assert((*ptep & PTE_P) != 0); + + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { + cprintf("SWAP: failed to save\n"); + sm->map_swappable(mm, v, page, 0); + continue; + } + else { + cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; + free_page(page); + } + + tlb_invalidate(mm->pgdir, v); + } + return i; +} + +int +swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) +{ + struct Page *result = alloc_page(); + assert(result!=NULL); + + pte_t *ptep = get_pte(mm->pgdir, addr, 0); + // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); + + int r; + if ((r = swapfs_read((*ptep), result)) != 0) + { + assert(r!=0); + } + cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr); + *ptr_result=result; + return 0; +} + + + +static inline void +check_content_set(void) +{ + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x1010 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x2010 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x3010 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + *(unsigned char *)0x4010 = 0x0d; + assert(pgfault_num==4); +} + +static inline int +check_content_access(void) +{ + int ret = sm->check_swap(); + return ret; +} + +struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM]; +pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM]; +unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM]; + +extern free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +check_swap(void) +{ + //backup mem env + int ret, count = 0, total = 0, i; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + //assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + + //now we set the phy pages env + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + extern struct mm_struct *check_mm_struct; + assert(check_mm_struct == NULL); + + check_mm_struct = mm; + + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + //setup the temp Page Table vaddr 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + pte_t *temp_ptep=NULL; + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); + assert(temp_ptep!= NULL); + cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + + for (i=0;iphy_page environment for page relpacement algorithm + + + pgfault_num=0; + + check_content_set(); + assert( nr_free == 0); + for(i = 0; ipgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + nr_free = nr_free_store; + free_list = free_list_store; + + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + cprintf("count is %d, total is %d\n",count,total); + //assert(count == 0); + + cprintf("check_swap() succeeded!\n"); +} diff --git a/code/lab6/kern/mm/swap.h b/code/lab6/kern/mm/swap.h new file mode 100644 index 0000000..5d4aea8 --- /dev/null +++ b/code/lab6/kern/mm/swap.h @@ -0,0 +1,65 @@ +#ifndef __KERN_MM_SWAP_H__ +#define __KERN_MM_SWAP_H__ + +#include +#include +#include +#include + +/* * + * swap_entry_t + * -------------------------------------------- + * | offset | reserved | 0 | + * -------------------------------------------- + * 24 bits 7 bits 1 bit + * */ + +#define MAX_SWAP_OFFSET_LIMIT (1 << 24) + +extern size_t max_swap_offset; + +/* * + * swap_offset - takes a swap_entry (saved in pte), and returns + * the corresponding offset in swap mem_map. + * */ +#define swap_offset(entry) ({ \ + size_t __offset = (entry >> 8); \ + if (!(__offset > 0 && __offset < max_swap_offset)) { \ + panic("invalid swap_entry_t = %08x.\n", entry); \ + } \ + __offset; \ + }) + +struct swap_manager +{ + const char *name; + /* Global initialization for the swap manager */ + int (*init) (void); + /* Initialize the priv data inside mm_struct */ + int (*init_mm) (struct mm_struct *mm); + /* Called when tick interrupt occured */ + int (*tick_event) (struct mm_struct *mm); + /* Called when map a swappable page into the mm_struct */ + int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); + /* When a page is marked as shared, this routine is called to + * delete the addr entry from the swap manager */ + int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); + /* Try to swap out a page, return then victim */ + int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); + /* check the page relpacement algorithm */ + int (*check_swap)(void); +}; + +extern volatile int swap_init_ok; +int swap_init(void); +int swap_init_mm(struct mm_struct *mm); +int swap_tick_event(struct mm_struct *mm); +int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); +int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr); +int swap_out(struct mm_struct *mm, int n, int in_tick); +int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result); + +//#define MEMBER_OFFSET(m,t) ((int)(&((t *)0)->m)) +//#define FROM_MEMBER(m,t,a) ((t *)((char *)(a) - MEMBER_OFFSET(m,t))) + +#endif diff --git a/code/lab6/kern/mm/swap_fifo.c b/code/lab6/kern/mm/swap_fifo.c new file mode 100644 index 0000000..4cb00c1 --- /dev/null +++ b/code/lab6/kern/mm/swap_fifo.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out + * page replacement algorithm is a low-overhead algorithm that requires little book-keeping on + * the part of the operating system. The idea is obvious from the name - the operating system + * keeps track of all the pages in memory in a queue, with the most recent arrival at the back, + * and the earliest arrival in front. When a page needs to be replaced, the page at the front + * of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs + * poorly in practical application. Thus, it is rarely used in its unmodified form. This + * algorithm experiences Belady's anomaly. + * + * Details of FIFO PRA + * (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can + * link these pages into pra_list_head according the time order. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list + * implementation. You should know howto USE: list_init, list_add(list_add_after), + * list_add_before, list_del, list_next, list_prev. Another tricky method is to transform + * a general list struct to a special struct (such as struct page). You can find some MACRO: + * le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc. + */ + +list_entry_t pra_list_head; +/* + * (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head. + * Now, From the memory control struct mm_struct, we can access FIFO PRA + */ +static int +_fifo_init_mm(struct mm_struct *mm) +{ + list_init(&pra_list_head); + mm->sm_priv = &pra_list_head; + //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + return 0; +} +/* + * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue + */ +static int +_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + list_entry_t *entry=&(page->pra_page_link); + + assert(entry != NULL && head != NULL); + //record the page access situlation + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + return 0; +} +/* + * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, + * then set the addr of addr of this page to ptr_page. + */ +static int +_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + assert(head != NULL); + assert(in_tick==0); + /* Select the victim */ + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1) unlink the earliest arrival page in front of pra_list_head qeueue + //(2) set the addr of addr of this page to ptr_page + return 0; +} + +static int +_fifo_check_swap(void) { + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==4); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==4); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==4); + cprintf("write Virt Page e in fifo_check_swap\n"); + *(unsigned char *)0x5000 = 0x0e; + assert(pgfault_num==5); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==5); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==6); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==7); + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==8); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==9); + return 0; +} + + +static int +_fifo_init(void) +{ + return 0; +} + +static int +_fifo_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return 0; +} + +static int +_fifo_tick_event(struct mm_struct *mm) +{ return 0; } + + +struct swap_manager swap_manager_fifo = +{ + .name = "fifo swap manager", + .init = &_fifo_init, + .init_mm = &_fifo_init_mm, + .tick_event = &_fifo_tick_event, + .map_swappable = &_fifo_map_swappable, + .set_unswappable = &_fifo_set_unswappable, + .swap_out_victim = &_fifo_swap_out_victim, + .check_swap = &_fifo_check_swap, +}; diff --git a/code/lab6/kern/mm/swap_fifo.h b/code/lab6/kern/mm/swap_fifo.h new file mode 100644 index 0000000..1d74269 --- /dev/null +++ b/code/lab6/kern/mm/swap_fifo.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_SWAP_FIFO_H__ +#define __KERN_MM_SWAP_FIFO_H__ + +#include +extern struct swap_manager swap_manager_fifo; + +#endif diff --git a/code/lab6/kern/mm/vmm.c b/code/lab6/kern/mm/vmm.c new file mode 100644 index 0000000..8051479 --- /dev/null +++ b/code/lab6/kern/mm/vmm.c @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + vmm design include two parts: mm_struct (mm) & vma_struct (vma) + mm is the memory manager for the set of continuous virtual memory + area which have the same PDT. vma is a continuous virtual memory area. + There a linear link list for vma & a redblack link list for vma in mm. +--------------- + mm related functions: + golbal functions + struct mm_struct * mm_create(void) + void mm_destroy(struct mm_struct *mm) + int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) +-------------- + vma related functions: + global functions + struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) + void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) + struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) + local functions + inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) +--------------- + check correctness functions + void check_vmm(void); + void check_vma_struct(void); + void check_pgfault(void); +*/ + +static void check_vmm(void); +static void check_vma_struct(void); +static void check_pgfault(void); + +// mm_create - alloc a mm_struct & initialize it. +struct mm_struct * +mm_create(void) { + struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); + + if (mm != NULL) { + list_init(&(mm->mmap_list)); + mm->mmap_cache = NULL; + mm->pgdir = NULL; + mm->map_count = 0; + + if (swap_init_ok) swap_init_mm(mm); + else mm->sm_priv = NULL; + + set_mm_count(mm, 0); + lock_init(&(mm->mm_lock)); + } + return mm; +} + +// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +struct vma_struct * +vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); + + if (vma != NULL) { + vma->vm_start = vm_start; + vma->vm_end = vm_end; + vma->vm_flags = vm_flags; + } + return vma; +} + + +// find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +struct vma_struct * +find_vma(struct mm_struct *mm, uintptr_t addr) { + struct vma_struct *vma = NULL; + if (mm != NULL) { + vma = mm->mmap_cache; + if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { + bool found = 0; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + vma = le2vma(le, list_link); + if (addr < vma->vm_end) { + found = 1; + break; + } + } + if (!found) { + vma = NULL; + } + } + if (vma != NULL) { + mm->mmap_cache = vma; + } + } + return vma; +} + + +// check_vma_overlap - check if vma1 overlaps vma2 ? +static inline void +check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { + assert(prev->vm_start < prev->vm_end); + assert(prev->vm_end <= next->vm_start); + assert(next->vm_start < next->vm_end); +} + + +// insert_vma_struct -insert vma in mm's list link +void +insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + assert(vma->vm_start < vma->vm_end); + list_entry_t *list = &(mm->mmap_list); + list_entry_t *le_prev = list, *le_next; + + list_entry_t *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *mmap_prev = le2vma(le, list_link); + if (mmap_prev->vm_start > vma->vm_start) { + break; + } + le_prev = le; + } + + le_next = list_next(le_prev); + + /* check overlap */ + if (le_prev != list) { + check_vma_overlap(le2vma(le_prev, list_link), vma); + } + if (le_next != list) { + check_vma_overlap(vma, le2vma(le_next, list_link)); + } + + vma->vm_mm = mm; + list_add_after(le_prev, &(vma->list_link)); + + mm->map_count ++; +} + +// mm_destroy - free mm and mm internal fields +void +mm_destroy(struct mm_struct *mm) { + assert(mm_count(mm) == 0); + + list_entry_t *list = &(mm->mmap_list), *le; + while ((le = list_next(list)) != list) { + list_del(le); + kfree(le2vma(le, list_link)); //kfree vma + } + kfree(mm); //kfree mm + mm=NULL; +} + +int +mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store) { + uintptr_t start = ROUNDDOWN(addr, PGSIZE), end = ROUNDUP(addr + len, PGSIZE); + if (!USER_ACCESS(start, end)) { + return -E_INVAL; + } + + assert(mm != NULL); + + int ret = -E_INVAL; + + struct vma_struct *vma; + if ((vma = find_vma(mm, start)) != NULL && end > vma->vm_start) { + goto out; + } + ret = -E_NO_MEM; + + if ((vma = vma_create(start, end, vm_flags)) == NULL) { + goto out; + } + insert_vma_struct(mm, vma); + if (vma_store != NULL) { + *vma_store = vma; + } + ret = 0; + +out: + return ret; +} + +int +dup_mmap(struct mm_struct *to, struct mm_struct *from) { + assert(to != NULL && from != NULL); + list_entry_t *list = &(from->mmap_list), *le = list; + while ((le = list_prev(le)) != list) { + struct vma_struct *vma, *nvma; + vma = le2vma(le, list_link); + nvma = vma_create(vma->vm_start, vma->vm_end, vma->vm_flags); + if (nvma == NULL) { + return -E_NO_MEM; + } + + insert_vma_struct(to, nvma); + + bool share = 0; + if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share) != 0) { + return -E_NO_MEM; + } + } + return 0; +} + +void +exit_mmap(struct mm_struct *mm) { + assert(mm != NULL && mm_count(mm) == 0); + pde_t *pgdir = mm->pgdir; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + unmap_range(pgdir, vma->vm_start, vma->vm_end); + } + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + exit_range(pgdir, vma->vm_start, vma->vm_end); + } +} + +bool +copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable) { + if (!user_mem_check(mm, (uintptr_t)src, len, writable)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +bool +copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len) { + if (!user_mem_check(mm, (uintptr_t)dst, len, 1)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +// vmm_init - initialize virtual memory management +// - now just call check_vmm to check correctness of vmm +void +vmm_init(void) { + check_vmm(); +} + +// check_vmm - check correctness of vmm +static void +check_vmm(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_vma_struct(); + check_pgfault(); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vmm() succeeded.\n"); +} + +static void +check_vma_struct(void) { + size_t nr_free_pages_store = nr_free_pages(); + + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + int step1 = 10, step2 = step1 * 10; + + int i; + for (i = step1; i >= 0; i --) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + for (i = step1 + 1; i <= step2; i ++) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + list_entry_t *le = list_next(&(mm->mmap_list)); + + for (i = 0; i <= step2; i ++) { + assert(le != &(mm->mmap_list)); + struct vma_struct *mmap = le2vma(le, list_link); + assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); + le = list_next(le); + } + + for (i = 0; i < 5 * step2 + 2; i ++) { + struct vma_struct *vma = find_vma(mm, i); + assert(vma != NULL); + int j = i / 5; + if (i >= 5 * j + 2) { + j ++; + } + assert(vma->vm_start == j * 5 && vma->vm_end == j * 5 + 2); + } + + mm_destroy(mm); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vma_struct() succeeded!\n"); +} + +struct mm_struct *check_mm_struct; + +// check_pgfault - check correctness of pgfault handler +static void +check_pgfault(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_mm_struct = mm_create(); + assert(check_mm_struct != NULL); + + struct mm_struct *mm = check_mm_struct; + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + uintptr_t addr = 0x100; + assert(find_vma(mm, addr) == vma); + + int i, sum = 0; + for (i = 0; i < 100; i ++) { + *(char *)(addr + i) = i; + sum += i; + } + for (i = 0; i < 100; i ++) { + sum -= *(char *)(addr + i); + } + assert(sum == 0); + + page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + free_page(pa2page(pgdir[0])); + pgdir[0] = 0; + + mm->pgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_pgfault() succeeded!\n"); +} +//page fault number +volatile unsigned int pgfault_num=0; + +/* do_pgfault - interrupt handler to process the page fault execption + * @mm : the control struct for a set of vma using the same PDT + * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware + * @addr : the addr which causes a memory access exception, (the contents of the CR2 register) + * + * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault + * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing + * the exception and recovering from it. + * (1) The contents of the CR2 register. The processor loads the CR2 register with the + * 32-bit linear address that generated the exception. The do_pgfault fun can + * use this address to locate the corresponding page directory and page-table + * entries. + * (2) An error code on the kernel stack. The error code for a page fault has a format different from + * that for other exceptions. The error code tells the exception handler three things: + * -- The P flag (bit 0) indicates whether the exception was due to a not-present page (0) + * or to either an access rights violation or the use of a reserved bit (1). + * -- The W/R flag (bit 1) indicates whether the memory access that caused the exception + * was a read (0) or write (1). + * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) + * or supervisor mode (0) at the time of the exception. + */ +int +do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { + int ret = -E_INVAL; + //try to find a vma which include addr + struct vma_struct *vma = find_vma(mm, addr); + + pgfault_num++; + //If the addr is in the range of a mm's vma? + if (vma == NULL || vma->vm_start > addr) { + cprintf("not valid addr %x, and can not find it in vma\n", addr); + goto failed; + } + //check the error_code + switch (error_code & 3) { + default: + /* error code flag : default is 3 ( W/R=1, P=1): write, present */ + case 2: /* error code flag : (W/R=1, P=0): write, not present */ + if (!(vma->vm_flags & VM_WRITE)) { + cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); + goto failed; + } + break; + case 1: /* error code flag : (W/R=0, P=1): read, present */ + cprintf("do_pgfault failed: error code flag = read AND present\n"); + goto failed; + case 0: /* error code flag : (W/R=0, P=0): read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { + cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); + goto failed; + } + } + /* IF (write an existed addr ) OR + * (write an non_existed addr && addr is writable) OR + * (read an non_existed addr && addr is readable) + * THEN + * continue process + */ + uint32_t perm = PTE_U; + if (vma->vm_flags & VM_WRITE) { + perm |= PTE_W; + } + addr = ROUNDDOWN(addr, PGSIZE); + + ret = -E_NO_MEM; + + pte_t *ptep=NULL; + /*LAB3 EXERCISE 1: YOUR CODE + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * get_pte : get an pte and return the kernel virtual address of this pte for la + * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') + * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup + * an addr map pa<--->la with linear address la and the PDT pgdir + * DEFINES: + * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + * VARIABLES: + * mm->pgdir : the PDT of these vma + * + */ +#if 0 + /*LAB3 EXERCISE 1: YOUR CODE*/ + ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + if (*ptep == 0) { + //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + + } + else { + /*LAB3 EXERCISE 2: YOUR CODE + * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, + * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, + * find the addr of disk page, read the content of disk page into this memroy page + * page_insert : build the map of phy addr of an Page with the linear addr la + * swap_map_swappable : set the page swappable + */ + if(swap_init_ok) { + struct Page *page=NULL; + //(1)According to the mm AND addr, try to load the content of right disk page + // into the memory which page managed. + //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr + //(3) make the page swappable. + //(4) [NOTICE]: you myabe need to update your lab3's implementation for LAB5's normal execution. + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed; + } + } +#endif + ret = 0; +failed: + return ret; +} + +bool +user_mem_check(struct mm_struct *mm, uintptr_t addr, size_t len, bool write) { + if (mm != NULL) { + if (!USER_ACCESS(addr, addr + len)) { + return 0; + } + struct vma_struct *vma; + uintptr_t start = addr, end = addr + len; + while (start < end) { + if ((vma = find_vma(mm, start)) == NULL || start < vma->vm_start) { + return 0; + } + if (!(vma->vm_flags & ((write) ? VM_WRITE : VM_READ))) { + return 0; + } + if (write && (vma->vm_flags & VM_STACK)) { + if (start < vma->vm_start + PGSIZE) { //check stack start & size + return 0; + } + } + start = vma->vm_end; + } + return 1; + } + return KERN_ACCESS(addr, addr + len); +} + diff --git a/code/lab6/kern/mm/vmm.h b/code/lab6/kern/mm/vmm.h new file mode 100644 index 0000000..b2abfdd --- /dev/null +++ b/code/lab6/kern/mm/vmm.h @@ -0,0 +1,100 @@ +#ifndef __KERN_MM_VMM_H__ +#define __KERN_MM_VMM_H__ + +#include +#include +#include +#include + +//pre define +struct mm_struct; + +// the virtual continuous memory area(vma) +struct vma_struct { + struct mm_struct *vm_mm; // the set of vma using the same PDT + uintptr_t vm_start; // start addr of vma + uintptr_t vm_end; // end addr of vma + uint32_t vm_flags; // flags of vma + list_entry_t list_link; // linear list link which sorted by start addr of vma +}; + +#define le2vma(le, member) \ + to_struct((le), struct vma_struct, member) + +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 +#define VM_STACK 0x00000008 + +// the control struct for a set of vma using the same PDT +struct mm_struct { + list_entry_t mmap_list; // linear list link which sorted by start addr of vma + struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose + pde_t *pgdir; // the PDT of these vma + int map_count; // the count of these vma + void *sm_priv; // the private data for swap manager + atomic_t mm_count; // the number ofprocess which shared the mm + lock_t mm_lock; // mutex for using dup_mmap fun to duplicat the mm +}; + +struct vma_struct *find_vma(struct mm_struct *mm, uintptr_t addr); +struct vma_struct *vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags); +void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma); + +struct mm_struct *mm_create(void); +void mm_destroy(struct mm_struct *mm); + +void vmm_init(void); +int mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store); +int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr); + +int mm_unmap(struct mm_struct *mm, uintptr_t addr, size_t len); +int dup_mmap(struct mm_struct *to, struct mm_struct *from); +void exit_mmap(struct mm_struct *mm); +uintptr_t get_unmapped_area(struct mm_struct *mm, size_t len); +int mm_brk(struct mm_struct *mm, uintptr_t addr, size_t len); + +extern volatile unsigned int pgfault_num; +extern struct mm_struct *check_mm_struct; + +bool user_mem_check(struct mm_struct *mm, uintptr_t start, size_t len, bool write); +bool copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable); +bool copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len); + +static inline int +mm_count(struct mm_struct *mm) { + return atomic_read(&(mm->mm_count)); +} + +static inline void +set_mm_count(struct mm_struct *mm, int val) { + atomic_set(&(mm->mm_count), val); +} + +static inline int +mm_count_inc(struct mm_struct *mm) { + return atomic_add_return(&(mm->mm_count), 1); +} + +static inline int +mm_count_dec(struct mm_struct *mm) { + return atomic_sub_return(&(mm->mm_count), 1); +} + +static inline void +lock_mm(struct mm_struct *mm) { + if (mm != NULL) { + lock(&(mm->mm_lock)); + } +} + +static inline void +unlock_mm(struct mm_struct *mm) { + if (mm != NULL) { + unlock(&(mm->mm_lock)); + } +} + +#endif /* !__KERN_MM_VMM_H__ */ + diff --git a/code/lab6/kern/process/entry.S b/code/lab6/kern/process/entry.S new file mode 100644 index 0000000..7482e23 --- /dev/null +++ b/code/lab6/kern/process/entry.S @@ -0,0 +1,10 @@ +.text +.globl kernel_thread_entry +kernel_thread_entry: # void kernel_thread(void) + + pushl %edx # push arg + call *%ebx # call fn + + pushl %eax # save the return value of fn(arg) + call do_exit # call do_exit to terminate current thread + diff --git a/code/lab6/kern/process/proc.c b/code/lab6/kern/process/proc.c new file mode 100644 index 0000000..b878aff --- /dev/null +++ b/code/lab6/kern/process/proc.c @@ -0,0 +1,849 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------- process/thread mechanism design&implementation ------------- +(an simplified Linux process/thread mechanism ) +introduction: + ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads +for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to +manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory). +------------------------------ +process state : meaning -- reason + PROC_UNINIT : uninitialized -- alloc_proc + PROC_SLEEPING : sleeping -- try_free_pages, do_wait, do_sleep + PROC_RUNNABLE : runnable(maybe running) -- proc_init, wakeup_proc, + PROC_ZOMBIE : almost dead -- do_exit + +----------------------------- +process state changing: + + alloc_proc RUNNING + + +--<----<--+ + + + proc_run + + V +-->---->--+ +PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING -- + A + + + | +--- do_exit --> PROC_ZOMBIE + + + + + -----------------------wakeup_proc---------------------------------- +----------------------------- +process relations +parent: proc->parent (proc is children) +children: proc->cptr (proc is parent) +older sibling: proc->optr (proc is younger sibling) +younger sibling: proc->yptr (proc is older sibling) +----------------------------- +related syscall for process: +SYS_exit : process exit, -->do_exit +SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc +SYS_wait : wait process -->do_wait +SYS_exec : after fork, process execute a program -->load a program and refresh the mm +SYS_clone : create child thread -->do_fork-->wakeup_proc +SYS_yield : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process +SYS_sleep : process sleep -->do_sleep +SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING + -->wakeup_proc-->do_wait-->do_exit +SYS_getpid : get the process's pid + +*/ + +// the process set's list +list_entry_t proc_list; + +#define HASH_SHIFT 10 +#define HASH_LIST_SIZE (1 << HASH_SHIFT) +#define pid_hashfn(x) (hash32(x, HASH_SHIFT)) + +// has list for process set based on pid +static list_entry_t hash_list[HASH_LIST_SIZE]; + +// idle proc +struct proc_struct *idleproc = NULL; +// init proc +struct proc_struct *initproc = NULL; +// current proc +struct proc_struct *current = NULL; + +static int nr_process = 0; + +void kernel_thread_entry(void); +void forkrets(struct trapframe *tf); +void switch_to(struct context *from, struct context *to); + +// alloc_proc - alloc a proc_struct and init all fields of proc_struct +static struct proc_struct * +alloc_proc(void) { + struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); + if (proc != NULL) { + //LAB4:EXERCISE1 YOUR CODE + /* + * below fields in proc_struct need to be initialized + * enum proc_state state; // Process state + * int pid; // Process ID + * int runs; // the running times of Proces + * uintptr_t kstack; // Process kernel stack + * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + * struct proc_struct *parent; // the parent process + * struct mm_struct *mm; // Process's memory management field + * struct context context; // Switch here to run process + * struct trapframe *tf; // Trap frame for current interrupt + * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + * uint32_t flags; // Process flag + * char name[PROC_NAME_LEN + 1]; // Process name + */ + } + return proc; +} + +// set_proc_name - set the name of proc +char * +set_proc_name(struct proc_struct *proc, const char *name) { + memset(proc->name, 0, sizeof(proc->name)); + return memcpy(proc->name, name, PROC_NAME_LEN); +} + +// get_proc_name - get the name of proc +char * +get_proc_name(struct proc_struct *proc) { + static char name[PROC_NAME_LEN + 1]; + memset(name, 0, sizeof(name)); + return memcpy(name, proc->name, PROC_NAME_LEN); +} + +// set_links - set the relation links of process +static void +set_links(struct proc_struct *proc) { + list_add(&proc_list, &(proc->list_link)); + proc->yptr = NULL; + if ((proc->optr = proc->parent->cptr) != NULL) { + proc->optr->yptr = proc; + } + proc->parent->cptr = proc; + nr_process ++; +} + +// remove_links - clean the relation links of process +static void +remove_links(struct proc_struct *proc) { + list_del(&(proc->list_link)); + if (proc->optr != NULL) { + proc->optr->yptr = proc->yptr; + } + if (proc->yptr != NULL) { + proc->yptr->optr = proc->optr; + } + else { + proc->parent->cptr = proc->optr; + } + nr_process --; +} + +// get_pid - alloc a unique pid for process +static int +get_pid(void) { + static_assert(MAX_PID > MAX_PROCESS); + struct proc_struct *proc; + list_entry_t *list = &proc_list, *le; + static int next_safe = MAX_PID, last_pid = MAX_PID; + if (++ last_pid >= MAX_PID) { + last_pid = 1; + goto inside; + } + if (last_pid >= next_safe) { + inside: + next_safe = MAX_PID; + repeat: + le = list; + while ((le = list_next(le)) != list) { + proc = le2proc(le, list_link); + if (proc->pid == last_pid) { + if (++ last_pid >= next_safe) { + if (last_pid >= MAX_PID) { + last_pid = 1; + } + next_safe = MAX_PID; + goto repeat; + } + } + else if (proc->pid > last_pid && next_safe > proc->pid) { + next_safe = proc->pid; + } + } + } + return last_pid; +} + +// proc_run - make process "proc" running on cpu +// NOTE: before call switch_to, should load base addr of "proc"'s new PDT +void +proc_run(struct proc_struct *proc) { + if (proc != current) { + bool intr_flag; + struct proc_struct *prev = current, *next = proc; + local_intr_save(intr_flag); + { + current = proc; + load_esp0(next->kstack + KSTACKSIZE); + lcr3(next->cr3); + switch_to(&(prev->context), &(next->context)); + } + local_intr_restore(intr_flag); + } +} + +// forkret -- the first kernel entry point of a new thread/process +// NOTE: the addr of forkret is setted in copy_thread function +// after switch_to, the current proc will execute here. +static void +forkret(void) { + forkrets(current->tf); +} + +// hash_proc - add proc into proc hash_list +static void +hash_proc(struct proc_struct *proc) { + list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); +} + +// unhash_proc - delete proc from proc hash_list +static void +unhash_proc(struct proc_struct *proc) { + list_del(&(proc->hash_link)); +} + +// find_proc - find proc frome proc hash_list according to pid +struct proc_struct * +find_proc(int pid) { + if (0 < pid && pid < MAX_PID) { + list_entry_t *list = hash_list + pid_hashfn(pid), *le = list; + while ((le = list_next(le)) != list) { + struct proc_struct *proc = le2proc(le, hash_link); + if (proc->pid == pid) { + return proc; + } + } + } + return NULL; +} + +// kernel_thread - create a kernel thread using "fn" function +// NOTE: the contents of temp trapframe tf will be copied to +// proc->tf in do_fork-->copy_thread function +int +kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { + struct trapframe tf; + memset(&tf, 0, sizeof(struct trapframe)); + tf.tf_cs = KERNEL_CS; + tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; + tf.tf_regs.reg_ebx = (uint32_t)fn; + tf.tf_regs.reg_edx = (uint32_t)arg; + tf.tf_eip = (uint32_t)kernel_thread_entry; + return do_fork(clone_flags | CLONE_VM, 0, &tf); +} + +// setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack +static int +setup_kstack(struct proc_struct *proc) { + struct Page *page = alloc_pages(KSTACKPAGE); + if (page != NULL) { + proc->kstack = (uintptr_t)page2kva(page); + return 0; + } + return -E_NO_MEM; +} + +// put_kstack - free the memory space of process kernel stack +static void +put_kstack(struct proc_struct *proc) { + free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE); +} + +// setup_pgdir - alloc one page as PDT +static int +setup_pgdir(struct mm_struct *mm) { + struct Page *page; + if ((page = alloc_page()) == NULL) { + return -E_NO_MEM; + } + pde_t *pgdir = page2kva(page); + memcpy(pgdir, boot_pgdir, PGSIZE); + pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_P | PTE_W; + mm->pgdir = pgdir; + return 0; +} + +// put_pgdir - free the memory space of PDT +static void +put_pgdir(struct mm_struct *mm) { + free_page(kva2page(mm->pgdir)); +} + +// copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags +// - if clone_flags & CLONE_VM, then "share" ; else "duplicate" +static int +copy_mm(uint32_t clone_flags, struct proc_struct *proc) { + struct mm_struct *mm, *oldmm = current->mm; + + /* current is a kernel thread */ + if (oldmm == NULL) { + return 0; + } + if (clone_flags & CLONE_VM) { + mm = oldmm; + goto good_mm; + } + + int ret = -E_NO_MEM; + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + + lock_mm(oldmm); + { + ret = dup_mmap(mm, oldmm); + } + unlock_mm(oldmm); + + if (ret != 0) { + goto bad_dup_cleanup_mmap; + } + +good_mm: + mm_count_inc(mm); + proc->mm = mm; + proc->cr3 = PADDR(mm->pgdir); + return 0; +bad_dup_cleanup_mmap: + exit_mmap(mm); + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + return ret; +} + +// copy_thread - setup the trapframe on the process's kernel stack top and +// - setup the kernel entry point and stack of process +static void +copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { + proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; + *(proc->tf) = *tf; + proc->tf->tf_regs.reg_eax = 0; + proc->tf->tf_esp = esp; + proc->tf->tf_eflags |= FL_IF; + + proc->context.eip = (uintptr_t)forkret; + proc->context.esp = (uintptr_t)(proc->tf); +} + +/* do_fork - parent process for a new child process + * @clone_flags: used to guide how to clone the child process + * @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread. + * @tf: the trapframe info, which will be copied to child process's proc->tf + */ +int +do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { + int ret = -E_NO_FREE_PROC; + struct proc_struct *proc; + if (nr_process >= MAX_PROCESS) { + goto fork_out; + } + ret = -E_NO_MEM; + //LAB4:EXERCISE2 YOUR CODE + /* + * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * alloc_proc: create a proc struct and init fields (lab4:exercise1) + * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack + * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags + * if clone_flags & CLONE_VM, then "share" ; else "duplicate" + * copy_thread: setup the trapframe on the process's kernel stack top and + * setup the kernel entry point and stack of process + * hash_proc: add proc into proc hash_list + * get_pid: alloc a unique pid for process + * wakup_proc: set proc->state = PROC_RUNNABLE + * VARIABLES: + * proc_list: the process set's list + * nr_process: the number of process set + */ + + // 1. call alloc_proc to allocate a proc_struct + // 2. call setup_kstack to allocate a kernel stack for child process + // 3. call copy_mm to dup OR share mm according clone_flag + // 4. call copy_thread to setup tf & context in proc_struct + // 5. insert proc_struct into hash_list && proc_list + // 6. call wakup_proc to make the new child process RUNNABLE + // 7. set ret vaule using child proc's pid +fork_out: + return ret; + +bad_fork_cleanup_kstack: + put_kstack(proc); +bad_fork_cleanup_proc: + kfree(proc); + goto fork_out; +} + +// do_exit - called by sys_exit +// 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process +// 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself. +// 3. call scheduler to switch to other process +int +do_exit(int error_code) { + if (current == idleproc) { + panic("idleproc exit.\n"); + } + if (current == initproc) { + panic("initproc exit.\n"); + } + + struct mm_struct *mm = current->mm; + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + current->state = PROC_ZOMBIE; + current->exit_code = error_code; + + bool intr_flag; + struct proc_struct *proc; + local_intr_save(intr_flag); + { + proc = current->parent; + if (proc->wait_state == WT_CHILD) { + wakeup_proc(proc); + } + while (current->cptr != NULL) { + proc = current->cptr; + current->cptr = proc->optr; + + proc->yptr = NULL; + if ((proc->optr = initproc->cptr) != NULL) { + initproc->cptr->yptr = proc; + } + proc->parent = initproc; + initproc->cptr = proc; + if (proc->state == PROC_ZOMBIE) { + if (initproc->wait_state == WT_CHILD) { + wakeup_proc(initproc); + } + } + } + } + local_intr_restore(intr_flag); + + schedule(); + panic("do_exit will not return!! %d.\n", current->pid); +} + +/* load_icode - load the content of binary program(ELF format) as the new content of current process + * @binary: the memory addr of the content of binary program + * @size: the size of the content of binary program + */ +static int +load_icode(unsigned char *binary, size_t size) { + if (current->mm != NULL) { + panic("load_icode: current->mm must be empty.\n"); + } + + int ret = -E_NO_MEM; + struct mm_struct *mm; + //(1) create a new mm for current process + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + //(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + //(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process + struct Page *page; + //(3.1) get the file header of the bianry program (ELF format) + struct elfhdr *elf = (struct elfhdr *)binary; + //(3.2) get the entry of the program section headers of the bianry program (ELF format) + struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff); + //(3.3) This program is valid? + if (elf->e_magic != ELF_MAGIC) { + ret = -E_INVAL_ELF; + goto bad_elf_cleanup_pgdir; + } + + uint32_t vm_flags, perm; + struct proghdr *ph_end = ph + elf->e_phnum; + for (; ph < ph_end; ph ++) { + //(3.4) find every program section headers + if (ph->p_type != ELF_PT_LOAD) { + continue ; + } + if (ph->p_filesz > ph->p_memsz) { + ret = -E_INVAL_ELF; + goto bad_cleanup_mmap; + } + if (ph->p_filesz == 0) { + continue ; + } + //(3.5) call mm_map fun to setup the new vma ( ph->p_va, ph->p_memsz) + vm_flags = 0, perm = PTE_U; + if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC; + if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE; + if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ; + if (vm_flags & VM_WRITE) perm |= PTE_W; + if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) { + goto bad_cleanup_mmap; + } + unsigned char *from = binary + ph->p_offset; + size_t off, size; + uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE); + + ret = -E_NO_MEM; + + //(3.6) alloc memory, and copy the contents of every program section (from, from+end) to process's memory (la, la+end) + end = ph->p_va + ph->p_filesz; + //(3.6.1) copy TEXT/DATA section of bianry program + while (start < end) { + if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { + goto bad_cleanup_mmap; + } + off = start - la, size = PGSIZE - off, la += PGSIZE; + if (end < la) { + size -= la - end; + } + memcpy(page2kva(page) + off, from, size); + start += size, from += size; + } + + //(3.6.2) build BSS section of binary program + end = ph->p_va + ph->p_memsz; + if (start < la) { + /* ph->p_memsz == ph->p_filesz */ + if (start == end) { + continue ; + } + off = start + PGSIZE - la, size = PGSIZE - off; + if (end < la) { + size -= la - end; + } + memset(page2kva(page) + off, 0, size); + start += size; + assert((end < la && start == end) || (end >= la && start == la)); + } + while (start < end) { + if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { + goto bad_cleanup_mmap; + } + off = start - la, size = PGSIZE - off, la += PGSIZE; + if (end < la) { + size -= la - end; + } + memset(page2kva(page) + off, 0, size); + start += size; + } + } + //(4) build user stack memory + vm_flags = VM_READ | VM_WRITE | VM_STACK; + if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) { + goto bad_cleanup_mmap; + } + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL); + + //(5) set current process's mm, sr3, and set CR3 reg = physical addr of Page Directory + mm_count_inc(mm); + current->mm = mm; + current->cr3 = PADDR(mm->pgdir); + lcr3(PADDR(mm->pgdir)); + + //(6) setup trapframe for user environment + struct trapframe *tf = current->tf; + memset(tf, 0, sizeof(struct trapframe)); + /* LAB5:EXERCISE1 YOUR CODE + * should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags + * NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So + * tf_cs should be USER_CS segment (see memlayout.h) + * tf_ds=tf_es=tf_ss should be USER_DS segment + * tf_esp should be the top addr of user stack (USTACKTOP) + * tf_eip should be the entry point of this binary program (elf->e_entry) + * tf_eflags should be set to enable computer to produce Interrupt + */ + ret = 0; +out: + return ret; +bad_cleanup_mmap: + exit_mmap(mm); +bad_elf_cleanup_pgdir: + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + goto out; +} + +// do_execve - call exit_mmap(mm)&pug_pgdir(mm) to reclaim memory space of current process +// - call load_icode to setup new memory space accroding binary prog. +int +do_execve(const char *name, size_t len, unsigned char *binary, size_t size) { + struct mm_struct *mm = current->mm; + if (!user_mem_check(mm, (uintptr_t)name, len, 0)) { + return -E_INVAL; + } + if (len > PROC_NAME_LEN) { + len = PROC_NAME_LEN; + } + + char local_name[PROC_NAME_LEN + 1]; + memset(local_name, 0, sizeof(local_name)); + memcpy(local_name, name, len); + + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + int ret; + if ((ret = load_icode(binary, size)) != 0) { + goto execve_exit; + } + set_proc_name(current, local_name); + return 0; + +execve_exit: + do_exit(ret); + panic("already exit: %e.\n", ret); +} + +// do_yield - ask the scheduler to reschedule +int +do_yield(void) { + current->need_resched = 1; + return 0; +} + +// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack +// - proc struct of this child. +// NOTE: only after do_wait function, all resources of the child proces are free. +int +do_wait(int pid, int *code_store) { + struct mm_struct *mm = current->mm; + if (code_store != NULL) { + if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) { + return -E_INVAL; + } + } + + struct proc_struct *proc; + bool intr_flag, haskid; +repeat: + haskid = 0; + if (pid != 0) { + proc = find_proc(pid); + if (proc != NULL && proc->parent == current) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + else { + proc = current->cptr; + for (; proc != NULL; proc = proc->optr) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + if (haskid) { + current->state = PROC_SLEEPING; + current->wait_state = WT_CHILD; + schedule(); + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + goto repeat; + } + return -E_BAD_PROC; + +found: + if (proc == idleproc || proc == initproc) { + panic("wait idleproc or initproc.\n"); + } + if (code_store != NULL) { + *code_store = proc->exit_code; + } + local_intr_save(intr_flag); + { + unhash_proc(proc); + remove_links(proc); + } + local_intr_restore(intr_flag); + put_kstack(proc); + kfree(proc); + return 0; +} + +// do_kill - kill process with pid by set this process's flags with PF_EXITING +int +do_kill(int pid) { + struct proc_struct *proc; + if ((proc = find_proc(pid)) != NULL) { + if (!(proc->flags & PF_EXITING)) { + proc->flags |= PF_EXITING; + if (proc->wait_state & WT_INTERRUPTED) { + wakeup_proc(proc); + } + return 0; + } + return -E_KILLED; + } + return -E_INVAL; +} + +// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread +static int +kernel_execve(const char *name, unsigned char *binary, size_t size) { + int ret, len = strlen(name); + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (len), "b" (binary), "D" (size) + : "memory"); + return ret; +} + +#define __KERNEL_EXECVE(name, binary, size) ({ \ + cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \ + current->pid, name); \ + kernel_execve(name, binary, (size_t)(size)); \ + }) + +#define KERNEL_EXECVE(x) ({ \ + extern unsigned char _binary_obj___user_##x##_out_start[], \ + _binary_obj___user_##x##_out_size[]; \ + __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start, \ + _binary_obj___user_##x##_out_size); \ + }) + +#define __KERNEL_EXECVE2(x, xstart, xsize) ({ \ + extern unsigned char xstart[], xsize[]; \ + __KERNEL_EXECVE(#x, xstart, (size_t)xsize); \ + }) + +#define KERNEL_EXECVE2(x, xstart, xsize) __KERNEL_EXECVE2(x, xstart, xsize) + +// user_main - kernel thread used to exec a user program +static int +user_main(void *arg) { +#ifdef TEST + KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE); +#else + KERNEL_EXECVE(exit); +#endif + panic("user_main execve failed.\n"); +} + +// init_main - the second kernel thread used to create user_main kernel threads +static int +init_main(void *arg) { + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = kallocated(); + + int pid = kernel_thread(user_main, NULL, 0); + if (pid <= 0) { + panic("create user_main failed.\n"); + } + + while (do_wait(0, NULL) == 0) { + schedule(); + } + + cprintf("all user-mode processes have quit.\n"); + assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL); + assert(nr_process == 2); + assert(list_next(&proc_list) == &(initproc->list_link)); + assert(list_prev(&proc_list) == &(initproc->list_link)); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == kallocated()); + cprintf("init check memory pass.\n"); + return 0; +} + +// proc_init - set up the first kernel thread idleproc "idle" by itself and +// - create the second kernel thread init_main +void +proc_init(void) { + int i; + + list_init(&proc_list); + for (i = 0; i < HASH_LIST_SIZE; i ++) { + list_init(hash_list + i); + } + + if ((idleproc = alloc_proc()) == NULL) { + panic("cannot alloc idleproc.\n"); + } + + idleproc->pid = 0; + idleproc->state = PROC_RUNNABLE; + idleproc->kstack = (uintptr_t)bootstack; + idleproc->need_resched = 1; + set_proc_name(idleproc, "idle"); + nr_process ++; + + current = idleproc; + + int pid = kernel_thread(init_main, NULL, 0); + if (pid <= 0) { + panic("create init_main failed.\n"); + } + + initproc = find_proc(pid); + set_proc_name(initproc, "init"); + + assert(idleproc != NULL && idleproc->pid == 0); + assert(initproc != NULL && initproc->pid == 1); +} + +// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works +void +cpu_idle(void) { + while (1) { + if (current->need_resched) { + schedule(); + } + } +} + +//FOR LAB6, set the process's priority (bigger value will get more CPU time) +void +lab6_set_priority(uint32_t priority) +{ + if (priority == 0) + current->lab6_priority = 1; + else current->lab6_priority = priority; +} diff --git a/code/lab6/kern/process/proc.h b/code/lab6/kern/process/proc.h new file mode 100644 index 0000000..26abc49 --- /dev/null +++ b/code/lab6/kern/process/proc.h @@ -0,0 +1,99 @@ +#ifndef __KERN_PROCESS_PROC_H__ +#define __KERN_PROCESS_PROC_H__ + +#include +#include +#include +#include +#include + + +// process's state in his life cycle +enum proc_state { + PROC_UNINIT = 0, // uninitialized + PROC_SLEEPING, // sleeping + PROC_RUNNABLE, // runnable(maybe running) + PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource +}; + +// Saved registers for kernel context switches. +// Don't need to save all the %fs etc. segment registers, +// because they are constant across kernel contexts. +// Save all the regular registers so we don't need to care +// which are caller save, but not the return register %eax. +// (Not saving %eax just simplifies the switching code.) +// The layout of context must match code in switch.S. +struct context { + uint32_t eip; + uint32_t esp; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t esi; + uint32_t edi; + uint32_t ebp; +}; + +#define PROC_NAME_LEN 15 +#define MAX_PROCESS 4096 +#define MAX_PID (MAX_PROCESS * 2) + +extern list_entry_t proc_list; + +struct proc_struct { + enum proc_state state; // Process state + int pid; // Process ID + int runs; // the running times of Proces + uintptr_t kstack; // Process kernel stack + volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + struct proc_struct *parent; // the parent process + struct mm_struct *mm; // Process's memory management field + struct context context; // Switch here to run process + struct trapframe *tf; // Trap frame for current interrupt + uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + uint32_t flags; // Process flag + char name[PROC_NAME_LEN + 1]; // Process name + list_entry_t list_link; // Process link list + list_entry_t hash_link; // Process hash list + int exit_code; // exit code (be sent to parent proc) + uint32_t wait_state; // waiting state + struct proc_struct *cptr, *yptr, *optr; // relations between processes + struct run_queue *rq; // running queue contains Process + list_entry_t run_link; // the entry linked in run queue + int time_slice; // time slice for occupying the CPU + skew_heap_entry_t lab6_run_pool; // FOR LAB6 ONLY: the entry in the run pool + uint32_t lab6_stride; // FOR LAB6 ONLY: the current stride of the process + uint32_t lab6_priority; // FOR LAB6 ONLY: the priority of process, set by lab6_set_priority(uint32_t) +}; + +#define PF_EXITING 0x00000001 // getting shutdown + +#define WT_CHILD (0x00000001 | WT_INTERRUPTED) +#define WT_INTERRUPTED 0x80000000 // the wait state could be interrupted + + +#define le2proc(le, member) \ + to_struct((le), struct proc_struct, member) + +extern struct proc_struct *idleproc, *initproc, *current; + +void proc_init(void); +void proc_run(struct proc_struct *proc); +int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags); + +char *set_proc_name(struct proc_struct *proc, const char *name); +char *get_proc_name(struct proc_struct *proc); +void cpu_idle(void) __attribute__((noreturn)); + +struct proc_struct *find_proc(int pid); +int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf); +int do_exit(int error_code); +int do_yield(void); +int do_execve(const char *name, size_t len, unsigned char *binary, size_t size); +int do_wait(int pid, int *code_store); +int do_kill(int pid); +//FOR LAB6, set the process's priority (bigger value will get more CPU time) +void lab6_set_priority(uint32_t priority); + +#endif /* !__KERN_PROCESS_PROC_H__ */ + diff --git a/code/lab6/kern/process/switch.S b/code/lab6/kern/process/switch.S new file mode 100644 index 0000000..27b4c8c --- /dev/null +++ b/code/lab6/kern/process/switch.S @@ -0,0 +1,30 @@ +.text +.globl switch_to +switch_to: # switch_to(from, to) + + # save from's registers + movl 4(%esp), %eax # eax points to from + popl 0(%eax) # save eip !popl + movl %esp, 4(%eax) + movl %ebx, 8(%eax) + movl %ecx, 12(%eax) + movl %edx, 16(%eax) + movl %esi, 20(%eax) + movl %edi, 24(%eax) + movl %ebp, 28(%eax) + + # restore to's registers + movl 4(%esp), %eax # not 8(%esp): popped return address already + # eax now points to to + movl 28(%eax), %ebp + movl 24(%eax), %edi + movl 20(%eax), %esi + movl 16(%eax), %edx + movl 12(%eax), %ecx + movl 8(%eax), %ebx + movl 4(%eax), %esp + + pushl 0(%eax) # push eip + + ret + diff --git a/code/lab6/kern/schedule/default_sched.c b/code/lab6/kern/schedule/default_sched.c new file mode 100644 index 0000000..2316990 --- /dev/null +++ b/code/lab6/kern/schedule/default_sched.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +static void +RR_init(struct run_queue *rq) { + list_init(&(rq->run_list)); + rq->proc_num = 0; +} + +static void +RR_enqueue(struct run_queue *rq, struct proc_struct *proc) { + assert(list_empty(&(proc->run_link))); + list_add_before(&(rq->run_list), &(proc->run_link)); + if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) { + proc->time_slice = rq->max_time_slice; + } + proc->rq = rq; + rq->proc_num ++; +} + +static void +RR_dequeue(struct run_queue *rq, struct proc_struct *proc) { + assert(!list_empty(&(proc->run_link)) && proc->rq == rq); + list_del_init(&(proc->run_link)); + rq->proc_num --; +} + +static struct proc_struct * +RR_pick_next(struct run_queue *rq) { + list_entry_t *le = list_next(&(rq->run_list)); + if (le != &(rq->run_list)) { + return le2proc(le, run_link); + } + return NULL; +} + +static void +RR_proc_tick(struct run_queue *rq, struct proc_struct *proc) { + if (proc->time_slice > 0) { + proc->time_slice --; + } + if (proc->time_slice == 0) { + proc->need_resched = 1; + } +} + +struct sched_class default_sched_class = { + .name = "RR_scheduler", + .init = RR_init, + .enqueue = RR_enqueue, + .dequeue = RR_dequeue, + .pick_next = RR_pick_next, + .proc_tick = RR_proc_tick, +}; + diff --git a/code/lab6/kern/schedule/default_sched.h b/code/lab6/kern/schedule/default_sched.h new file mode 100644 index 0000000..2f21fbd --- /dev/null +++ b/code/lab6/kern/schedule/default_sched.h @@ -0,0 +1,9 @@ +#ifndef __KERN_SCHEDULE_SCHED_RR_H__ +#define __KERN_SCHEDULE_SCHED_RR_H__ + +#include + +extern struct sched_class default_sched_class; + +#endif /* !__KERN_SCHEDULE_SCHED_RR_H__ */ + diff --git a/code/lab6/kern/schedule/default_sched_stride_c b/code/lab6/kern/schedule/default_sched_stride_c new file mode 100644 index 0000000..7075653 --- /dev/null +++ b/code/lab6/kern/schedule/default_sched_stride_c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +#define USE_SKEW_HEAP 1 + +/* You should define the BigStride constant here*/ +/* LAB6: YOUR CODE */ +#define BIG_STRIDE /* you should give a value, and is ??? */ + +/* The compare function for two skew_heap_node_t's and the + * corresponding procs*/ +static int +proc_stride_comp_f(void *a, void *b) +{ + struct proc_struct *p = le2proc(a, lab6_run_pool); + struct proc_struct *q = le2proc(b, lab6_run_pool); + int32_t c = p->lab6_stride - q->lab6_stride; + if (c > 0) return 1; + else if (c == 0) return 0; + else return -1; +} + +/* + * stride_init initializes the run-queue rq with correct assignment for + * member variables, including: + * + * - run_list: should be a empty list after initialization. + * - lab6_run_pool: NULL + * - proc_num: 0 + * - max_time_slice: no need here, the variable would be assigned by the caller. + * + * hint: see libs/list.h for routines of the list structures. + */ +static void +stride_init(struct run_queue *rq) { + /* LAB6: YOUR CODE + * (1) init the ready process list: rq->run_list + * (2) init the run pool: rq->lab6_run_pool + * (3) set number of process: rq->proc_num to 0 + */ +} + +/* + * stride_enqueue inserts the process ``proc'' into the run-queue + * ``rq''. The procedure should verify/initialize the relevant members + * of ``proc'', and then put the ``lab6_run_pool'' node into the + * queue(since we use priority queue here). The procedure should also + * update the meta date in ``rq'' structure. + * + * proc->time_slice denotes the time slices allocation for the + * process, which should set to rq->max_time_slice. + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static void +stride_enqueue(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE + * (1) insert the proc into rq correctly + * NOTICE: you can use skew_heap or list. Important functions + * skew_heap_insert: insert a entry into skew_heap + * list_add_before: insert a entry into the last of list + * (2) recalculate proc->time_slice + * (3) set proc->rq pointer to rq + * (4) increase rq->proc_num + */ +} + +/* + * stride_dequeue removes the process ``proc'' from the run-queue + * ``rq'', the operation would be finished by the skew_heap_remove + * operations. Remember to update the ``rq'' structure. + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static void +stride_dequeue(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE + * (1) remove the proc from rq correctly + * NOTICE: you can use skew_heap or list. Important functions + * skew_heap_remove: remove a entry from skew_heap + * list_del_init: remove a entry from the list + */ +} +/* + * stride_pick_next pick the element from the ``run-queue'', with the + * minimum value of stride, and returns the corresponding process + * pointer. The process pointer would be calculated by macro le2proc, + * see kern/process/proc.h for definition. Return NULL if + * there is no process in the queue. + * + * When one proc structure is selected, remember to update the stride + * property of the proc. (stride += BIG_STRIDE / priority) + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static struct proc_struct * +stride_pick_next(struct run_queue *rq) { + /* LAB6: YOUR CODE + * (1) get a proc_struct pointer p with the minimum value of stride + (1.1) If using skew_heap, we can use le2proc get the p from rq->lab6_run_poll + (1.2) If using list, we have to search list to find the p with minimum stride value + * (2) update p;s stride value: p->lab6_stride + * (3) return p + */ +} + +/* + * stride_proc_tick works with the tick event of current process. You + * should check whether the time slices for current process is + * exhausted and update the proc struct ``proc''. proc->time_slice + * denotes the time slices left for current + * process. proc->need_resched is the flag variable for process + * switching. + */ +static void +stride_proc_tick(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE */ +} + +struct sched_class default_sched_class = { + .name = "stride_scheduler", + .init = stride_init, + .enqueue = stride_enqueue, + .dequeue = stride_dequeue, + .pick_next = stride_pick_next, + .proc_tick = stride_proc_tick, +}; diff --git a/code/lab6/kern/schedule/sched.c b/code/lab6/kern/schedule/sched.c new file mode 100644 index 0000000..e272635 --- /dev/null +++ b/code/lab6/kern/schedule/sched.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include + +static list_entry_t timer_list; + +static struct sched_class *sched_class; + +static struct run_queue *rq; + +static inline void +sched_class_enqueue(struct proc_struct *proc) { + if (proc != idleproc) { + sched_class->enqueue(rq, proc); + } +} + +static inline void +sched_class_dequeue(struct proc_struct *proc) { + sched_class->dequeue(rq, proc); +} + +static inline struct proc_struct * +sched_class_pick_next(void) { + return sched_class->pick_next(rq); +} + +static void +sched_class_proc_tick(struct proc_struct *proc) { + if (proc != idleproc) { + sched_class->proc_tick(rq, proc); + } + else { + proc->need_resched = 1; + } +} + +static struct run_queue __rq; + +void +sched_init(void) { + list_init(&timer_list); + + sched_class = &default_sched_class; + + rq = &__rq; + rq->max_time_slice = 20; + sched_class->init(rq); + + cprintf("sched class: %s\n", sched_class->name); +} + +void +wakeup_proc(struct proc_struct *proc) { + assert(proc->state != PROC_ZOMBIE); + bool intr_flag; + local_intr_save(intr_flag); + { + if (proc->state != PROC_RUNNABLE) { + proc->state = PROC_RUNNABLE; + proc->wait_state = 0; + if (proc != current) { + sched_class_enqueue(proc); + } + } + else { + warn("wakeup runnable process.\n"); + } + } + local_intr_restore(intr_flag); +} + +void +schedule(void) { + bool intr_flag; + struct proc_struct *next; + local_intr_save(intr_flag); + { + current->need_resched = 0; + if (current->state == PROC_RUNNABLE) { + sched_class_enqueue(current); + } + if ((next = sched_class_pick_next()) != NULL) { + sched_class_dequeue(next); + } + if (next == NULL) { + next = idleproc; + } + next->runs ++; + if (next != current) { + proc_run(next); + } + } + local_intr_restore(intr_flag); +} + +void +add_timer(timer_t *timer) { + bool intr_flag; + local_intr_save(intr_flag); + { + assert(timer->expires > 0 && timer->proc != NULL); + assert(list_empty(&(timer->timer_link))); + list_entry_t *le = list_next(&timer_list); + while (le != &timer_list) { + timer_t *next = le2timer(le, timer_link); + if (timer->expires < next->expires) { + next->expires -= timer->expires; + break; + } + timer->expires -= next->expires; + le = list_next(le); + } + list_add_before(le, &(timer->timer_link)); + } + local_intr_restore(intr_flag); +} + +void +del_timer(timer_t *timer) { + bool intr_flag; + local_intr_save(intr_flag); + { + if (!list_empty(&(timer->timer_link))) { + if (timer->expires != 0) { + list_entry_t *le = list_next(&(timer->timer_link)); + if (le != &timer_list) { + timer_t *next = le2timer(le, timer_link); + next->expires += timer->expires; + } + } + list_del_init(&(timer->timer_link)); + } + } + local_intr_restore(intr_flag); +} + +void +run_timer_list(void) { + bool intr_flag; + local_intr_save(intr_flag); + { + list_entry_t *le = list_next(&timer_list); + if (le != &timer_list) { + timer_t *timer = le2timer(le, timer_link); + assert(timer->expires != 0); + timer->expires --; + while (timer->expires == 0) { + le = list_next(le); + struct proc_struct *proc = timer->proc; + if (proc->wait_state != 0) { + assert(proc->wait_state & WT_INTERRUPTED); + } + else { + warn("process %d's wait_state == 0.\n", proc->pid); + } + wakeup_proc(proc); + del_timer(timer); + if (le == &timer_list) { + break; + } + timer = le2timer(le, timer_link); + } + } + sched_class_proc_tick(current); + } + local_intr_restore(intr_flag); +} diff --git a/code/lab6/kern/schedule/sched.h b/code/lab6/kern/schedule/sched.h new file mode 100644 index 0000000..c83a776 --- /dev/null +++ b/code/lab6/kern/schedule/sched.h @@ -0,0 +1,70 @@ +#ifndef __KERN_SCHEDULE_SCHED_H__ +#define __KERN_SCHEDULE_SCHED_H__ + +#include +#include +#include + +struct proc_struct; + +typedef struct { + unsigned int expires; + struct proc_struct *proc; + list_entry_t timer_link; +} timer_t; + +#define le2timer(le, member) \ +to_struct((le), timer_t, member) + +static inline timer_t * +timer_init(timer_t *timer, struct proc_struct *proc, int expires) { + timer->expires = expires; + timer->proc = proc; + list_init(&(timer->timer_link)); + return timer; +} + +struct run_queue; + +// The introduction of scheduling classes is borrrowed from Linux, and makes the +// core scheduler quite extensible. These classes (the scheduler modules) encapsulate +// the scheduling policies. +struct sched_class { + // the name of sched_class + const char *name; + // Init the run queue + void (*init)(struct run_queue *rq); + // put the proc into runqueue, and this function must be called with rq_lock + void (*enqueue)(struct run_queue *rq, struct proc_struct *proc); + // get the proc out runqueue, and this function must be called with rq_lock + void (*dequeue)(struct run_queue *rq, struct proc_struct *proc); + // choose the next runnable task + struct proc_struct *(*pick_next)(struct run_queue *rq); + // dealer of the time-tick + void (*proc_tick)(struct run_queue *rq, struct proc_struct *proc); + /* for SMP support in the future + * load_balance + * void (*load_balance)(struct rq* rq); + * get some proc from this rq, used in load_balance, + * return value is the num of gotten proc + * int (*get_proc)(struct rq* rq, struct proc* procs_moved[]); + */ +}; + +struct run_queue { + list_entry_t run_list; + unsigned int proc_num; + int max_time_slice; + // For LAB6 ONLY + skew_heap_entry_t *lab6_run_pool; +}; + +void sched_init(void); +void wakeup_proc(struct proc_struct *proc); +void schedule(void); +void add_timer(timer_t *timer); +void del_timer(timer_t *timer); +void run_timer_list(void); + +#endif /* !__KERN_SCHEDULE_SCHED_H__ */ + diff --git a/code/lab6/kern/sync/sync.h b/code/lab6/kern/sync/sync.h new file mode 100644 index 0000000..3e75192 --- /dev/null +++ b/code/lab6/kern/sync/sync.h @@ -0,0 +1,57 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +typedef volatile bool lock_t; + +static inline void +lock_init(lock_t *lock) { + *lock = 0; +} + +static inline bool +try_lock(lock_t *lock) { + return !test_and_set_bit(0, lock); +} + +static inline void +lock(lock_t *lock) { + while (!try_lock(lock)) { + schedule(); + } +} + +static inline void +unlock(lock_t *lock) { + if (!test_and_clear_bit(0, lock)) { + panic("Unlock failed.\n"); + } +} + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab6/kern/syscall/syscall.c b/code/lab6/kern/syscall/syscall.c new file mode 100644 index 0000000..3d6d3ca --- /dev/null +++ b/code/lab6/kern/syscall/syscall.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int +sys_exit(uint32_t arg[]) { + int error_code = (int)arg[0]; + return do_exit(error_code); +} + +static int +sys_fork(uint32_t arg[]) { + struct trapframe *tf = current->tf; + uintptr_t stack = tf->tf_esp; + return do_fork(0, stack, tf); +} + +static int +sys_wait(uint32_t arg[]) { + int pid = (int)arg[0]; + int *store = (int *)arg[1]; + return do_wait(pid, store); +} + +static int +sys_exec(uint32_t arg[]) { + const char *name = (const char *)arg[0]; + size_t len = (size_t)arg[1]; + unsigned char *binary = (unsigned char *)arg[2]; + size_t size = (size_t)arg[3]; + return do_execve(name, len, binary, size); +} + +static int +sys_yield(uint32_t arg[]) { + return do_yield(); +} + +static int +sys_kill(uint32_t arg[]) { + int pid = (int)arg[0]; + return do_kill(pid); +} + +static int +sys_getpid(uint32_t arg[]) { + return current->pid; +} + +static int +sys_putc(uint32_t arg[]) { + int c = (int)arg[0]; + cputchar(c); + return 0; +} + +static int +sys_pgdir(uint32_t arg[]) { + print_pgdir(); + return 0; +} + +static uint32_t +sys_gettime(uint32_t arg[]) { + return (int)ticks; +} +static uint32_t +sys_lab6_set_priority(uint32_t arg[]) +{ + uint32_t priority = (uint32_t)arg[0]; + lab6_set_priority(priority); + return 0; +} + +static int (*syscalls[])(uint32_t arg[]) = { + [SYS_exit] sys_exit, + [SYS_fork] sys_fork, + [SYS_wait] sys_wait, + [SYS_exec] sys_exec, + [SYS_yield] sys_yield, + [SYS_kill] sys_kill, + [SYS_getpid] sys_getpid, + [SYS_putc] sys_putc, + [SYS_pgdir] sys_pgdir, + [SYS_gettime] sys_gettime, + [SYS_lab6_set_priority] sys_lab6_set_priority, +}; + +#define NUM_SYSCALLS ((sizeof(syscalls)) / (sizeof(syscalls[0]))) + +void +syscall(void) { + struct trapframe *tf = current->tf; + uint32_t arg[5]; + int num = tf->tf_regs.reg_eax; + if (num >= 0 && num < NUM_SYSCALLS) { + if (syscalls[num] != NULL) { + arg[0] = tf->tf_regs.reg_edx; + arg[1] = tf->tf_regs.reg_ecx; + arg[2] = tf->tf_regs.reg_ebx; + arg[3] = tf->tf_regs.reg_edi; + arg[4] = tf->tf_regs.reg_esi; + tf->tf_regs.reg_eax = syscalls[num](arg); + return ; + } + } + print_trapframe(tf); + panic("undefined syscall %d, pid = %d, name = %s.\n", + num, current->pid, current->name); +} + diff --git a/code/lab6/kern/syscall/syscall.h b/code/lab6/kern/syscall/syscall.h new file mode 100644 index 0000000..a8fe843 --- /dev/null +++ b/code/lab6/kern/syscall/syscall.h @@ -0,0 +1,7 @@ +#ifndef __KERN_SYSCALL_SYSCALL_H__ +#define __KERN_SYSCALL_SYSCALL_H__ + +void syscall(void); + +#endif /* !__KERN_SYSCALL_SYSCALL_H__ */ + diff --git a/code/lab6/kern/trap/trap.c b/code/lab6/kern/trap/trap.c new file mode 100644 index 0000000..e8eb143 --- /dev/null +++ b/code/lab6/kern/trap/trap.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ + /* LAB5 YOUR CODE */ + //you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore + //so you should setup the syscall interrupt gate in here +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static inline void +print_pgfault(struct trapframe *tf) { + /* error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * bit 2 == 0 means kernel, 1 means user + * */ + cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), + (tf->tf_err & 4) ? 'U' : 'K', + (tf->tf_err & 2) ? 'W' : 'R', + (tf->tf_err & 1) ? "protection fault" : "no page found"); +} + +static int +pgfault_handler(struct trapframe *tf) { + extern struct mm_struct *check_mm_struct; + if(check_mm_struct !=NULL) { //used for test check_swap + print_pgfault(tf); + } + struct mm_struct *mm; + if (check_mm_struct != NULL) { + assert(current == idleproc); + mm = check_mm_struct; + } + else { + if (current == NULL) { + print_trapframe(tf); + print_pgfault(tf); + panic("unhandled page fault.\n"); + } + mm = current->mm; + } + return do_pgfault(mm, tf->tf_err, rcr2()); +} + +static volatile int in_swap_tick_event = 0; +extern struct mm_struct *check_mm_struct; + +static void +trap_dispatch(struct trapframe *tf) { + char c; + + int ret=0; + + switch (tf->tf_trapno) { + case T_PGFLT: //page fault + if ((ret = pgfault_handler(tf)) != 0) { + print_trapframe(tf); + if (current == NULL) { + panic("handle pgfault failed. ret=%d\n", ret); + } + else { + if (trap_in_kernel(tf)) { + panic("handle pgfault failed in kernel mode. ret=%d\n", ret); + } + cprintf("killed by kernel.\n"); + panic("handle user mode pgfault failed. ret=%d\n", ret); + do_exit(-E_KILLED); + } + } + break; + case T_SYSCALL: + syscall(); + break; + case IRQ_OFFSET + IRQ_TIMER: +#if 0 + LAB3 : If some page replacement algorithm need tick to change the priority of pages, + then you can add code here. +#endif + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + /* LAB5 YOUR CODE */ + /* you should upate you lab1 code (just add ONE or TWO lines of code): + * Every TICK_NUM cycle, you should set current process's current->need_resched = 1 + */ + + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + print_trapframe(tf); + if (current != NULL) { + cprintf("unhandled trap.\n"); + do_exit(-E_KILLED); + } + // in kernel, it must be a mistake + panic("unexpected trap in kernel.\n"); + + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + // used for previous projects + if (current == NULL) { + trap_dispatch(tf); + } + else { + // keep a trapframe chain in stack + struct trapframe *otf = current->tf; + current->tf = tf; + + bool in_kernel = trap_in_kernel(tf); + + trap_dispatch(tf); + + current->tf = otf; + if (!in_kernel) { + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + if (current->need_resched) { + schedule(); + } + } + } +} + diff --git a/code/lab6/kern/trap/trap.h b/code/lab6/kern/trap/trap.h new file mode 100644 index 0000000..e870a6f --- /dev/null +++ b/code/lab6/kern/trap/trap.h @@ -0,0 +1,89 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab6/kern/trap/trapentry.S b/code/lab6/kern/trap/trapentry.S new file mode 100644 index 0000000..3565ec8 --- /dev/null +++ b/code/lab6/kern/trap/trapentry.S @@ -0,0 +1,49 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + +.globl forkrets +forkrets: + # set stack to this new process's trapframe + movl 4(%esp), %esp + jmp __trapret diff --git a/code/lab6/kern/trap/vectors.S b/code/lab6/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab6/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab6/libs/atomic.h b/code/lab6/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab6/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab6/libs/defs.h b/code/lab6/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab6/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab6/libs/elf.h b/code/lab6/libs/elf.h new file mode 100644 index 0000000..8678f10 --- /dev/null +++ b/code/lab6/libs/elf.h @@ -0,0 +1,48 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +/* values for Proghdr::p_type */ +#define ELF_PT_LOAD 1 + +/* flag bits for Proghdr::p_flags */ +#define ELF_PF_X 1 +#define ELF_PF_W 2 +#define ELF_PF_R 4 + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab6/libs/error.h b/code/lab6/libs/error.h new file mode 100644 index 0000000..ddad593 --- /dev/null +++ b/code/lab6/libs/error.h @@ -0,0 +1,33 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault +#define E_SWAP_FAULT 7 // SWAP READ/WRITE fault +#define E_INVAL_ELF 8 // Invalid elf file +#define E_KILLED 9 // Process is killed +#define E_PANIC 10 // Panic Failure +#define E_TIMEOUT 11 // Timeout +#define E_TOO_BIG 12 // Argument is Too Big +#define E_NO_DEV 13 // No such Device +#define E_NA_DEV 14 // Device Not Available +#define E_BUSY 15 // Device/File is Busy +#define E_NOENT 16 // No Such File or Directory +#define E_ISDIR 17 // Is a Directory +#define E_NOTDIR 18 // Not a Directory +#define E_XDEV 19 // Cross Device-Link +#define E_UNIMP 20 // Unimplemented Feature +#define E_SEEK 21 // Illegal Seek +#define E_MAX_OPEN 22 // Too Many Files are Open +#define E_EXISTS 23 // File/Directory Already Exists +#define E_NOTEMPTY 24 // Directory is Not Empty +/* the maximum allowed */ +#define MAXERROR 24 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab6/libs/hash.c b/code/lab6/libs/hash.c new file mode 100644 index 0000000..61bcd88 --- /dev/null +++ b/code/lab6/libs/hash.c @@ -0,0 +1,18 @@ +#include + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL + +/* * + * hash32 - generate a hash value in the range [0, 2^@bits - 1] + * @val: the input value + * @bits: the number of bits in a return value + * + * High bits are more random, so we use them. + * */ +uint32_t +hash32(uint32_t val, unsigned int bits) { + uint32_t hash = val * GOLDEN_RATIO_PRIME_32; + return (hash >> (32 - bits)); +} + diff --git a/code/lab6/libs/list.h b/code/lab6/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab6/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab6/libs/printfmt.c b/code/lab6/libs/printfmt.c new file mode 100644 index 0000000..a666a52 --- /dev/null +++ b/code/lab6/libs/printfmt.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", + [E_INVAL_ELF] "invalid elf file", + [E_KILLED] "process is killed", + [E_PANIC] "panic failure", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab6/libs/rand.c b/code/lab6/libs/rand.c new file mode 100644 index 0000000..2a2c4e7 --- /dev/null +++ b/code/lab6/libs/rand.c @@ -0,0 +1,26 @@ +#include +#include + +static unsigned long long next = 1; + +/* * + * rand - returns a pseudo-random integer + * + * The rand() function return a value in the range [0, RAND_MAX]. + * */ +int +rand(void) { + next = (next * 0x5DEECE66DLL + 0xBLL) & ((1LL << 48) - 1); + unsigned long long result = (next >> 12); + return (int)do_div(result, RAND_MAX + 1); +} + +/* * + * srand - seed the random number generator with the given number + * @seed: the required seed number + * */ +void +srand(unsigned int seed) { + next = seed; +} + diff --git a/code/lab6/libs/skew_heap.h b/code/lab6/libs/skew_heap.h new file mode 100644 index 0000000..0c216b8 --- /dev/null +++ b/code/lab6/libs/skew_heap.h @@ -0,0 +1,87 @@ +#ifndef __LIBS_SKEW_HEAP_H__ +#define __LIBS_SKEW_HEAP_H__ + +struct skew_heap_entry { + struct skew_heap_entry *parent, *left, *right; +}; + +typedef struct skew_heap_entry skew_heap_entry_t; + +typedef int(*compare_f)(void *a, void *b); + +static inline void skew_heap_init(skew_heap_entry_t *a) __attribute__((always_inline)); +static inline skew_heap_entry_t *skew_heap_merge( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp); +static inline skew_heap_entry_t *skew_heap_insert( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) __attribute__((always_inline)); +static inline skew_heap_entry_t *skew_heap_remove( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) __attribute__((always_inline)); + +static inline void +skew_heap_init(skew_heap_entry_t *a) +{ + a->left = a->right = a->parent = NULL; +} + +static inline skew_heap_entry_t * +skew_heap_merge(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + if (a == NULL) return b; + else if (b == NULL) return a; + + skew_heap_entry_t *l, *r; + if (comp(a, b) == -1) + { + r = a->left; + l = skew_heap_merge(a->right, b, comp); + + a->left = l; + a->right = r; + if (l) l->parent = a; + + return a; + } + else + { + r = b->left; + l = skew_heap_merge(a, b->right, comp); + + b->left = l; + b->right = r; + if (l) l->parent = b; + + return b; + } +} + +static inline skew_heap_entry_t * +skew_heap_insert(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + skew_heap_init(b); + return skew_heap_merge(a, b, comp); +} + +static inline skew_heap_entry_t * +skew_heap_remove(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + skew_heap_entry_t *p = b->parent; + skew_heap_entry_t *rep = skew_heap_merge(b->left, b->right, comp); + if (rep) rep->parent = p; + + if (p) + { + if (p->left == b) + p->left = rep; + else p->right = rep; + return a; + } + else return rep; +} + +#endif /* !__LIBS_SKEW_HEAP_H__ */ diff --git a/code/lab6/libs/stdarg.h b/code/lab6/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab6/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab6/libs/stdio.h b/code/lab6/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab6/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab6/libs/stdlib.h b/code/lab6/libs/stdlib.h new file mode 100644 index 0000000..51878ef --- /dev/null +++ b/code/lab6/libs/stdlib.h @@ -0,0 +1,17 @@ +#ifndef __LIBS_STDLIB_H__ +#define __LIBS_STDLIB_H__ + +#include + +/* the largest number rand will return */ +#define RAND_MAX 2147483647UL + +/* libs/rand.c */ +int rand(void); +void srand(unsigned int seed); + +/* libs/hash.c */ +uint32_t hash32(uint32_t val, unsigned int bits); + +#endif /* !__LIBS_RAND_H__ */ + diff --git a/code/lab6/libs/string.c b/code/lab6/libs/string.c new file mode 100644 index 0000000..bcf1b1c --- /dev/null +++ b/code/lab6/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab6/libs/string.h b/code/lab6/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab6/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab6/libs/unistd.h b/code/lab6/libs/unistd.h new file mode 100644 index 0000000..b4ceff9 --- /dev/null +++ b/code/lab6/libs/unistd.h @@ -0,0 +1,31 @@ +#ifndef __LIBS_UNISTD_H__ +#define __LIBS_UNISTD_H__ + +#define T_SYSCALL 0x80 + +/* syscall number */ +#define SYS_exit 1 +#define SYS_fork 2 +#define SYS_wait 3 +#define SYS_exec 4 +#define SYS_clone 5 +#define SYS_yield 10 +#define SYS_sleep 11 +#define SYS_kill 12 +#define SYS_gettime 17 +#define SYS_getpid 18 +#define SYS_brk 19 +#define SYS_mmap 20 +#define SYS_munmap 21 +#define SYS_shmem 22 +#define SYS_putc 30 +#define SYS_pgdir 31 +/* OLNY FOR LAB6 */ +#define SYS_lab6_set_priority 255 + +/* SYS_fork flags */ +#define CLONE_VM 0x00000100 // set if VM shared between processes +#define CLONE_THREAD 0x00000200 // thread group + +#endif /* !__LIBS_UNISTD_H__ */ + diff --git a/code/lab6/libs/x86.h b/code/lab6/libs/x86.h new file mode 100644 index 0000000..b29f671 --- /dev/null +++ b/code/lab6/libs/x86.h @@ -0,0 +1,302 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab6/tools/boot.ld b/code/lab6/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab6/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab6/tools/function.mk b/code/lab6/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab6/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab6/tools/gdbinit b/code/lab6/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab6/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab6/tools/grade.sh b/code/lab6/tools/grade.sh new file mode 100644 index 0000000..e56bbe0 --- /dev/null +++ b/code/lab6/tools/grade.sh @@ -0,0 +1,582 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-prog ] [-Ddef...] [-check ] checkargs ... + tag= + prog= + check=check_regexps + while true; do + select= + case $1 in + -tag|-prog) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + if [ -z "$prog" ]; then + $make $makeopts touch > /dev/null 2>&1 + args="$defs" + else + if [ -z "$tag" ]; then + tag="$prog" + fi + args="build-$prog $defs" + fi + + build_run "$tag" "$args" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## swap image +swapimg=$(make_print swapimg) + +## set default qemu-options +qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback" + +## set break-function, default is readline +brkfun=readline + +default_check() { + pts=7 + check_regexps "$@" + + pts=3 + quick_check 'check output' \ + 'memory management: default_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' \ + 'check_slab() succeeded!' \ + 'check_vma_struct() succeeded!' \ + 'page fault at 0x00000100: K/W [no page found].' \ + 'check_pgfault() succeeded!' \ + 'check_vmm() succeeded.' \ + 'page fault at 0x00001000: K/W [no page found].' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'write Virt Page e in fifo_check_swap' \ + 'page fault at 0x00005000: K/W [no page found].' \ + 'page fault at 0x00001000: K/W [no page found]' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'check_swap() succeeded!' \ + '++ setup timer interrupts' +} + +## check now!! + +run_test -prog 'badsegment' -check default_check \ + 'kernel_execve: pid = 2, name = "badsegment".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000028' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'divzero' -check default_check \ + 'kernel_execve: pid = 2, name = "divzero".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x00000000 Divide error' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'softint' -check default_check \ + 'kernel_execve: pid = 2, name = "softint".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000072' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'faultread' -check default_check \ + 'kernel_execve: pid = 2, name = "faultread".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000004' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'faultreadkernel' -check default_check \ + 'kernel_execve: pid = 2, name = "faultreadkernel".' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000005' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'hello' -check default_check \ + 'kernel_execve: pid = 2, name = "hello".' \ + 'Hello world!!.' \ + 'I am process 2.' \ + 'hello pass.' + +run_test -prog 'testbss' -check default_check \ + 'kernel_execve: pid = 2, name = "testbss".' \ + 'Making sure bss works right...' \ + 'Yes, good. Now doing a wild write off the end...' \ + 'testbss may pass.' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000006' \ + - ' eip 0x008.....' \ + 'killed by kernel.' \ + ! - 'user panic at .*' + +run_test -prog 'pgdir' -check default_check \ + 'kernel_execve: pid = 2, name = "pgdir".' \ + 'I am 2, print pgdir.' \ + 'PDE(001) 00800000-00c00000 00400000 urw' \ + ' |-- PTE(00002) 00800000-00802000 00002000 ur-' \ + ' |-- PTE(00001) 00802000-00803000 00001000 urw' \ + 'PDE(001) afc00000-b0000000 00400000 urw' \ + ' |-- PTE(00004) afffc000-b0000000 00004000 urw' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'pgdir pass.' + +run_test -prog 'yield' -check default_check \ + 'kernel_execve: pid = 2, name = "yield".' \ + 'Hello, I am process 2.' \ + 'Back in process 2, iteration 0.' \ + 'Back in process 2, iteration 1.' \ + 'Back in process 2, iteration 2.' \ + 'Back in process 2, iteration 3.' \ + 'Back in process 2, iteration 4.' \ + 'All done in process 2.' \ + 'yield pass.' + + +run_test -prog 'badarg' -check default_check \ + 'kernel_execve: pid = 2, name = "badarg".' \ + 'fork ok.' \ + 'badarg pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'exit' -check default_check \ + 'kernel_execve: pid = 2, name = "exit".' \ + 'I am the parent. Forking the child...' \ + 'I am the parent, waiting now..' \ + 'I am the child.' \ + - 'waitpid [0-9]+ ok\.' \ + 'exit pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'spin' -check default_check \ + 'kernel_execve: pid = 2, name = "spin".' \ + 'I am the parent. Forking the child...' \ + 'I am the parent. Running the child...' \ + 'I am the child. spinning ...' \ + 'I am the parent. Killing the child...' \ + 'kill returns 0' \ + 'wait returns 0' \ + 'spin may pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'waitkill' -check default_check \ + 'kernel_execve: pid = 2, name = "waitkill".' \ + 'wait child 1.' \ + 'child 2.' \ + 'child 1.' \ + 'kill parent ok.' \ + 'kill child1 ok.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=15 + +run_test -prog 'forktest' -check default_check \ + 'kernel_execve: pid = 2, name = "forktest".' \ + 'I am child 31' \ + 'I am child 19' \ + 'I am child 13' \ + 'I am child 0' \ + 'forktest pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'fork claimed to work [0-9]+ times!' \ + ! 'wait stopped early' \ + ! 'wait got too many' \ + ! - 'user panic at .*' + +pts=10 +run_test -prog 'forktree' -check default_check \ + 'kernel_execve: pid = 2, name = "forktree".' \ + - '....: I am '\'''\' \ + - '....: I am '\''0'\' \ + - '....: I am '\'''\' \ + - '....: I am '\''1'\' \ + - '....: I am '\''0'\' \ + - '....: I am '\''01'\' \ + - '....: I am '\''00'\' \ + - '....: I am '\''11'\' \ + - '....: I am '\''10'\' \ + - '....: I am '\''101'\' \ + - '....: I am '\''100'\' \ + - '....: I am '\''111'\' \ + - '....: I am '\''110'\' \ + - '....: I am '\''001'\' \ + - '....: I am '\''000'\' \ + - '....: I am '\''011'\' \ + - '....: I am '\''010'\' \ + - '....: I am '\''0101'\' \ + - '....: I am '\''0100'\' \ + - '....: I am '\''0111'\' \ + - '....: I am '\''0110'\' \ + - '....: I am '\''0001'\' \ + - '....: I am '\''0000'\' \ + - '....: I am '\''0011'\' \ + - '....: I am '\''0010'\' \ + - '....: I am '\''1101'\' \ + - '....: I am '\''1100'\' \ + - '....: I am '\''1111'\' \ + - '....: I am '\''1110'\' \ + - '....: I am '\''1001'\' \ + - '....: I am '\''1000'\' \ + - '....: I am '\''1011'\' \ + - '....: I am '\''1010'\' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' + +pts=20 +timeout=500 +run_test -prog 'matrix' -check default_check \ + 'kernel_execve: pid = 2, name = "matrix".' \ + 'fork ok.' \ + 'pid 4 done!.' \ + 'pid 7 done!.' \ + 'pid 13 done!.' \ + 'pid 17 done!.' \ + 'pid 23 done!.' \ + 'matrix pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=20 +timeout=150 +run_test -prog 'priority' -check default_check \ + 'sched class: stride_scheduler' \ + 'kernel_execve: pid = 2, name = "priority".' \ + 'main: fork ok,now need to wait pids.' \ + 'stride sched correct result: 1 2 3 4 5' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +## print final-score +show_final + diff --git a/code/lab6/tools/kernel.ld b/code/lab6/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab6/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab6/tools/sign.c b/code/lab6/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab6/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab6/tools/user.ld b/code/lab6/tools/user.ld new file mode 100644 index 0000000..c73b6fa --- /dev/null +++ b/code/lab6/tools/user.ld @@ -0,0 +1,71 @@ +/* Simple linker script for ucore user-level programs. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS { + /* Load programs at this address: "." means the current address */ + . = 0x800020; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + + /* Place debugging symbols so that they can be found by + * the kernel debugger. + * Specifically, the four words at 0x200000 mark the beginning of + * the stabs, the end of the stabs, the beginning of the stabs + * string table, and the end of the stabs string table, respectively. + */ + + .stab_info 0x200000 : { + LONG(__STAB_BEGIN__); + LONG(__STAB_END__); + LONG(__STABSTR_BEGIN__); + LONG(__STABSTR_END__); + } + + .stab : { + __STAB_BEGIN__ = DEFINED(__STAB_BEGIN__) ? __STAB_BEGIN__ : .; + *(.stab); + __STAB_END__ = DEFINED(__STAB_END__) ? __STAB_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + __STABSTR_BEGIN__ = DEFINED(__STABSTR_BEGIN__) ? __STABSTR_BEGIN__ : .; + *(.stabstr); + __STABSTR_END__ = DEFINED(__STABSTR_END__) ? __STABSTR_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack .comment) + } +} diff --git a/code/lab6/tools/vector.c b/code/lab6/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab6/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab6/user/badarg.c b/code/lab6/user/badarg.c new file mode 100644 index 0000000..7b4ffad --- /dev/null +++ b/code/lab6/user/badarg.c @@ -0,0 +1,22 @@ +#include +#include + +int +main(void) { + int pid, exit_code; + if ((pid = fork()) == 0) { + cprintf("fork ok.\n"); + int i; + for (i = 0; i < 10; i ++) { + yield(); + } + exit(0xbeaf); + } + assert(pid > 0); + assert(waitpid(-1, NULL) != 0); + assert(waitpid(pid, (void *)0xC0000000) != 0); + assert(waitpid(pid, &exit_code) == 0 && exit_code == 0xbeaf); + cprintf("badarg pass.\n"); + return 0; +} + diff --git a/code/lab6/user/badsegment.c b/code/lab6/user/badsegment.c new file mode 100644 index 0000000..cdd9e99 --- /dev/null +++ b/code/lab6/user/badsegment.c @@ -0,0 +1,11 @@ +#include +#include + +/* try to load the kernel's TSS selector into the DS register */ + +int +main(void) { + asm volatile("movw $0x28,%ax; movw %ax,%ds"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/divzero.c b/code/lab6/user/divzero.c new file mode 100644 index 0000000..16c23d5 --- /dev/null +++ b/code/lab6/user/divzero.c @@ -0,0 +1,11 @@ +#include +#include + +int zero; + +int +main(void) { + cprintf("value is %d.\n", 1 / zero); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/exit.c b/code/lab6/user/exit.c new file mode 100644 index 0000000..c3ac5f8 --- /dev/null +++ b/code/lab6/user/exit.c @@ -0,0 +1,34 @@ +#include +#include + +int magic = -0x10384; + +int +main(void) { + int pid, code; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child.\n"); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + exit(magic); + } + else { + cprintf("I am parent, fork a child pid %d\n",pid); + } + assert(pid > 0); + cprintf("I am the parent, waiting now..\n"); + + assert(waitpid(pid, &code) == 0 && code == magic); + assert(waitpid(pid, &code) != 0 && wait() != 0); + cprintf("waitpid %d ok.\n", pid); + + cprintf("exit pass.\n"); + return 0; +} + diff --git a/code/lab6/user/faultread.c b/code/lab6/user/faultread.c new file mode 100644 index 0000000..6d292e1 --- /dev/null +++ b/code/lab6/user/faultread.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %8x from 0.\n", *(unsigned int *)0); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/faultreadkernel.c b/code/lab6/user/faultreadkernel.c new file mode 100644 index 0000000..53457f6 --- /dev/null +++ b/code/lab6/user/faultreadkernel.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %08x from 0xfac00000!\n", *(unsigned *)0xfac00000); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/forktest.c b/code/lab6/user/forktest.c new file mode 100644 index 0000000..3eda228 --- /dev/null +++ b/code/lab6/user/forktest.c @@ -0,0 +1,34 @@ +#include +#include + +const int max_child = 32; + +int +main(void) { + int n, pid; + for (n = 0; n < max_child; n ++) { + if ((pid = fork()) == 0) { + cprintf("I am child %d\n", n); + exit(0); + } + assert(pid > 0); + } + + if (n > max_child) { + panic("fork claimed to work %d times!\n", n); + } + + for (; n > 0; n --) { + if (wait() != 0) { + panic("wait stopped early\n"); + } + } + + if (wait() == 0) { + panic("wait got too many\n"); + } + + cprintf("forktest pass.\n"); + return 0; +} + diff --git a/code/lab6/user/forktree.c b/code/lab6/user/forktree.c new file mode 100644 index 0000000..93f28bb --- /dev/null +++ b/code/lab6/user/forktree.c @@ -0,0 +1,37 @@ +#include +#include +#include + +#define DEPTH 4 + +void forktree(const char *cur); + +void +forkchild(const char *cur, char branch) { + char nxt[DEPTH + 1]; + + if (strlen(cur) >= DEPTH) + return; + + snprintf(nxt, DEPTH + 1, "%s%c", cur, branch); + if (fork() == 0) { + forktree(nxt); + yield(); + exit(0); + } +} + +void +forktree(const char *cur) { + cprintf("%04x: I am '%s'\n", getpid(), cur); + + forkchild(cur, '0'); + forkchild(cur, '1'); +} + +int +main(void) { + forktree(""); + return 0; +} + diff --git a/code/lab6/user/hello.c b/code/lab6/user/hello.c new file mode 100644 index 0000000..0f05251 --- /dev/null +++ b/code/lab6/user/hello.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("Hello world!!.\n"); + cprintf("I am process %d.\n", getpid()); + cprintf("hello pass.\n"); + return 0; +} + diff --git a/code/lab6/user/libs/initcode.S b/code/lab6/user/libs/initcode.S new file mode 100644 index 0000000..77a91d6 --- /dev/null +++ b/code/lab6/user/libs/initcode.S @@ -0,0 +1,14 @@ +.text +.globl _start +_start: + # set ebp for backtrace + movl $0x0, %ebp + + # move down the esp register + # since it may cause page fault in backtrace + subl $0x20, %esp + + # call user-program function + call umain +1: jmp 1b + diff --git a/code/lab6/user/libs/panic.c b/code/lab6/user/libs/panic.c new file mode 100644 index 0000000..783be16 --- /dev/null +++ b/code/lab6/user/libs/panic.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +void +__panic(const char *file, int line, const char *fmt, ...) { + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("user panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + exit(-E_PANIC); +} + +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("user warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + diff --git a/code/lab6/user/libs/stdio.c b/code/lab6/user/libs/stdio.c new file mode 100644 index 0000000..69b7749 --- /dev/null +++ b/code/lab6/user/libs/stdio.c @@ -0,0 +1,62 @@ +#include +#include +#include + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + sys_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + int cnt = vcprintf(fmt, ap); + va_end(ap); + + return cnt; +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + diff --git a/code/lab6/user/libs/syscall.c b/code/lab6/user/libs/syscall.c new file mode 100644 index 0000000..1571cc7 --- /dev/null +++ b/code/lab6/user/libs/syscall.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +#define MAX_ARGS 5 + +static inline int +syscall(int num, ...) { + va_list ap; + va_start(ap, num); + uint32_t a[MAX_ARGS]; + int i, ret; + for (i = 0; i < MAX_ARGS; i ++) { + a[i] = va_arg(ap, uint32_t); + } + va_end(ap); + + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), + "a" (num), + "d" (a[0]), + "c" (a[1]), + "b" (a[2]), + "D" (a[3]), + "S" (a[4]) + : "cc", "memory"); + return ret; +} + +int +sys_exit(int error_code) { + return syscall(SYS_exit, error_code); +} + +int +sys_fork(void) { + return syscall(SYS_fork); +} + +int +sys_wait(int pid, int *store) { + return syscall(SYS_wait, pid, store); +} + +int +sys_yield(void) { + return syscall(SYS_yield); +} + +int +sys_kill(int pid) { + return syscall(SYS_kill, pid); +} + +int +sys_getpid(void) { + return syscall(SYS_getpid); +} + +int +sys_putc(int c) { + return syscall(SYS_putc, c); +} + +int +sys_pgdir(void) { + return syscall(SYS_pgdir); +} + +size_t +sys_gettime(void) { + return syscall(SYS_gettime); +} + +void +sys_lab6_set_priority(uint32_t priority) +{ + syscall(SYS_lab6_set_priority, priority); +} diff --git a/code/lab6/user/libs/syscall.h b/code/lab6/user/libs/syscall.h new file mode 100644 index 0000000..d167dfe --- /dev/null +++ b/code/lab6/user/libs/syscall.h @@ -0,0 +1,16 @@ +#ifndef __USER_LIBS_SYSCALL_H__ +#define __USER_LIBS_SYSCALL_H__ + +int sys_exit(int error_code); +int sys_fork(void); +int sys_wait(int pid, int *store); +int sys_yield(void); +int sys_kill(int pid); +int sys_getpid(void); +int sys_putc(int c); +int sys_pgdir(void); +/* FOR LAB6 ONLY */ +void sys_lab6_set_priority(uint32_t priority); + +#endif /* !__USER_LIBS_SYSCALL_H__ */ + diff --git a/code/lab6/user/libs/ulib.c b/code/lab6/user/libs/ulib.c new file mode 100644 index 0000000..c17e754 --- /dev/null +++ b/code/lab6/user/libs/ulib.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +void +exit(int error_code) { + sys_exit(error_code); + cprintf("BUG: exit failed.\n"); + while (1); +} + +int +fork(void) { + return sys_fork(); +} + +int +wait(void) { + return sys_wait(0, NULL); +} + +int +waitpid(int pid, int *store) { + return sys_wait(pid, store); +} + +void +yield(void) { + sys_yield(); +} + +int +kill(int pid) { + return sys_kill(pid); +} + +int +getpid(void) { + return sys_getpid(); +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + sys_pgdir(); +} + +unsigned int +gettime_msec(void) { + return (unsigned int)sys_gettime(); +} + +void +lab6_set_priority(uint32_t priority) +{ + sys_lab6_set_priority(priority); +} diff --git a/code/lab6/user/libs/ulib.h b/code/lab6/user/libs/ulib.h new file mode 100644 index 0000000..dd57f80 --- /dev/null +++ b/code/lab6/user/libs/ulib.h @@ -0,0 +1,38 @@ +#ifndef __USER_LIBS_ULIB_H__ +#define __USER_LIBS_ULIB_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +void __noreturn exit(int error_code); +int fork(void); +int wait(void); +int waitpid(int pid, int *store); +void yield(void); +int kill(int pid); +int getpid(void); +void print_pgdir(void); +unsigned int gettime_msec(void); +void lab6_set_priority(uint32_t priority); + +#endif /* !__USER_LIBS_ULIB_H__ */ + diff --git a/code/lab6/user/libs/umain.c b/code/lab6/user/libs/umain.c new file mode 100644 index 0000000..c352072 --- /dev/null +++ b/code/lab6/user/libs/umain.c @@ -0,0 +1,10 @@ +#include + +int main(void); + +void +umain(void) { + int ret = main(); + exit(ret); +} + diff --git a/code/lab6/user/matrix.c b/code/lab6/user/matrix.c new file mode 100644 index 0000000..c5ec869 --- /dev/null +++ b/code/lab6/user/matrix.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#define MATSIZE 10 + +static int mata[MATSIZE][MATSIZE]; +static int matb[MATSIZE][MATSIZE]; +static int matc[MATSIZE][MATSIZE]; + +void +work(unsigned int times) { + int i, j, k, size = MATSIZE; + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + mata[i][j] = matb[i][j] = 1; + } + } + + yield(); + + cprintf("pid %d is running (%d times)!.\n", getpid(), times); + + while (times -- > 0) { + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + matc[i][j] = 0; + for (k = 0; k < size; k ++) { + matc[i][j] += mata[i][k] * matb[k][j]; + } + } + } + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + mata[i][j] = matb[i][j] = matc[i][j]; + } + } + } + cprintf("pid %d done!.\n", getpid()); + exit(0); +} + +const int total = 21; + +int +main(void) { + int pids[total]; + memset(pids, 0, sizeof(pids)); + + int i; + for (i = 0; i < total; i ++) { + if ((pids[i] = fork()) == 0) { + srand(i * i); + int times = (((unsigned int)rand()) % total); + times = (times * times + 10) * 100; + work(times); + } + if (pids[i] < 0) { + goto failed; + } + } + + cprintf("fork ok.\n"); + + for (i = 0; i < total; i ++) { + if (wait() != 0) { + cprintf("wait failed.\n"); + goto failed; + } + } + + cprintf("matrix pass.\n"); + return 0; + +failed: + for (i = 0; i < total; i ++) { + if (pids[i] > 0) { + kill(pids[i]); + } + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/pgdir.c b/code/lab6/user/pgdir.c new file mode 100644 index 0000000..09fd7e3 --- /dev/null +++ b/code/lab6/user/pgdir.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("I am %d, print pgdir.\n", getpid()); + print_pgdir(); + cprintf("pgdir pass.\n"); + return 0; +} + diff --git a/code/lab6/user/priority.c b/code/lab6/user/priority.c new file mode 100644 index 0000000..bcbc29d --- /dev/null +++ b/code/lab6/user/priority.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#define TOTAL 5 +/* to get enough accuracy, MAX_TIME (the running time of each process) should >1000 mseconds. */ +#define MAX_TIME 2000 +unsigned int acc[TOTAL]; +int status[TOTAL]; +int pids[TOTAL]; + +static void +spin_delay(void) +{ + int i; + volatile int j; + for (i = 0; i != 200; ++ i) + { + j = !j; + } +} + +int +main(void) { + int i,time; + memset(pids, 0, sizeof(pids)); + lab6_set_priority(TOTAL + 1); + + for (i = 0; i < TOTAL; i ++) { + acc[i]=0; + if ((pids[i] = fork()) == 0) { + lab6_set_priority(i + 1); + acc[i] = 0; + while (1) { + spin_delay(); + ++ acc[i]; + if(acc[i]%4000==0) { + if((time=gettime_msec())>MAX_TIME) { + cprintf("child pid %d, acc %d, time %d\n",getpid(),acc[i],time); + exit(acc[i]); + } + } + } + + } + if (pids[i] < 0) { + goto failed; + } + } + + cprintf("main: fork ok,now need to wait pids.\n"); + + for (i = 0; i < TOTAL; i ++) { + status[i]=0; + waitpid(pids[i],&status[i]); + cprintf("main: pid %d, acc %d, time %d\n",pids[i],status[i],gettime_msec()); + } + cprintf("main: wait pids over\n"); + cprintf("stride sched correct result:"); + for (i = 0; i < TOTAL; i ++) + { + cprintf(" %d", (status[i] * 2 / status[0] + 1) / 2); + } + cprintf("\n"); + + return 0; + +failed: + for (i = 0; i < TOTAL; i ++) { + if (pids[i] > 0) { + kill(pids[i]); + } + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/softint.c b/code/lab6/user/softint.c new file mode 100644 index 0000000..2f14d15 --- /dev/null +++ b/code/lab6/user/softint.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + asm volatile("int $14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/spin.c b/code/lab6/user/spin.c new file mode 100644 index 0000000..91581e5 --- /dev/null +++ b/code/lab6/user/spin.c @@ -0,0 +1,29 @@ +#include +#include + +int +main(void) { + int pid, ret; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child. spinning ...\n"); + while (1); + } + cprintf("I am the parent. Running the child...\n"); + + yield(); + yield(); + yield(); + + cprintf("I am the parent. Killing the child...\n"); + + assert((ret = kill(pid)) == 0); + cprintf("kill returns %d\n", ret); + + assert((ret = waitpid(pid, NULL)) == 0); + cprintf("wait returns %d\n", ret); + + cprintf("spin may pass.\n"); + return 0; +} + diff --git a/code/lab6/user/testbss.c b/code/lab6/user/testbss.c new file mode 100644 index 0000000..14dc6e1 --- /dev/null +++ b/code/lab6/user/testbss.c @@ -0,0 +1,33 @@ +#include +#include + +#define ARRAYSIZE (1024*1024) + +uint32_t bigarray[ARRAYSIZE]; + +int +main(void) { + cprintf("Making sure bss works right...\n"); + int i; + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != 0) { + panic("bigarray[%d] isn't cleared!\n", i); + } + } + for (i = 0; i < ARRAYSIZE; i ++) { + bigarray[i] = i; + } + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != i) { + panic("bigarray[%d] didn't hold its value!\n", i); + } + } + + cprintf("Yes, good. Now doing a wild write off the end...\n"); + cprintf("testbss may pass.\n"); + + bigarray[ARRAYSIZE + 1024] = 0; + asm volatile ("int $0x14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/waitkill.c b/code/lab6/user/waitkill.c new file mode 100644 index 0000000..9bb3f80 --- /dev/null +++ b/code/lab6/user/waitkill.c @@ -0,0 +1,59 @@ +#include +#include + +void +do_yield(void) { + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); +} + +int parent, pid1, pid2; + +void +loop(void) { + cprintf("child 1.\n"); + while (1); +} + +void +work(void) { + cprintf("child 2.\n"); + do_yield(); + if (kill(parent) == 0) { + cprintf("kill parent ok.\n"); + do_yield(); + if (kill(pid1) == 0) { + cprintf("kill child1 ok.\n"); + exit(0); + } + } + exit(-1); +} + +int +main(void) { + parent = getpid(); + if ((pid1 = fork()) == 0) { + loop(); + } + + assert(pid1 > 0); + + if ((pid2 = fork()) == 0) { + work(); + } + if (pid2 > 0) { + cprintf("wait child 1.\n"); + waitpid(pid1, NULL); + panic("waitpid %d returns\n", pid1); + } + else { + kill(pid1); + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab6/user/yield.c b/code/lab6/user/yield.c new file mode 100644 index 0000000..a19890d --- /dev/null +++ b/code/lab6/user/yield.c @@ -0,0 +1,16 @@ +#include +#include + +int +main(void) { + int i; + cprintf("Hello, I am process %d.\n", getpid()); + for (i = 0; i < 5; i ++) { + yield(); + cprintf("Back in process %d, iteration %d.\n", getpid(), i); + } + cprintf("All done in process %d.\n", getpid()); + cprintf("yield pass.\n"); + return 0; +} + diff --git a/code/lab7/Makefile b/code/lab7/Makefile new file mode 100644 index 0000000..823f109 --- /dev/null +++ b/code/lab7/Makefile @@ -0,0 +1,323 @@ +PROJ := 7 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 + +GDB := $(GCCPREFIX)gdb + +CC ?= $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +USER_PREFIX := __user_ + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) +filename = $(basename $(notdir $(1))) +ubinfile = $(call outfile,$(addprefix $(USER_PREFIX),$(call filename,$(1)))) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# user programs + +UINCLUDE += user/include/ \ + user/libs/ + +USRCDIR += user + +ULIBDIR += user/libs + +UCFLAGS += $(addprefix -I,$(UINCLUDE)) +USER_BINS := + +$(call add_files_cc,$(call listf_cc,$(ULIBDIR)),ulibs,$(UCFLAGS)) +$(call add_files_cc,$(call listf_cc,$(USRCDIR)),uprog,$(UCFLAGS)) + +UOBJS := $(call read_packet,ulibs libs) + +define uprog_ld +__user_bin__ := $$(call ubinfile,$(1)) +USER_BINS += $$(__user_bin__) +$$(__user_bin__): tools/user.ld +$$(__user_bin__): $$(UOBJS) +$$(__user_bin__): $(1) | $$$$(dir $$$$@) + $(V)$(LD) $(LDFLAGS) -T tools/user.ld -o $$@ $$(UOBJS) $(1) + @$(OBJDUMP) -S $$@ > $$(call cgtype,$$<,o,asm) + @$(OBJDUMP) -t $$@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$$$/d' > $$(call cgtype,$$<,o,sym) +endef + +$(foreach p,$(call read_packet,uprog),$(eval $(call uprog_ld,$(p)))) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ \ + kern/fs/ \ + kern/process \ + kern/schedule \ + kern/syscall + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync \ + kern/fs \ + kern/process \ + kern/schedule \ + kern/syscall + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) $(USER_BINS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) -b binary $(USER_BINS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- + +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# ------------------------------------------------------------------- + +# create swap.img +SWAPIMG := $(call totarget,swap.img) + +$(SWAPIMG): + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + +$(call create_target,swap.img) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + run-.+ \ + build-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback + +.PHONY: qemu qemu-nox debug debug-nox +qemu: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +TERMINAL := gnome-terminal + +debug: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) $(SWAPIMG) + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +RUN_PREFIX := _binary_$(OBJDIR)_$(USER_PREFIX) +MAKEOPTS := --quiet --no-print-directory + +run-%: build-% + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +build-%: touch + $(V)$(MAKE) $(MAKEOPTS) "DEFS+=-DTEST=$* -DTESTSTART=$(RUN_PREFIX)$*_out_start -DTESTSIZE=$(RUN_PREFIX)$*_out_size" + +.PHONY: grade touch + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := lab$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/process/proc.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab7/boot/asm.h b/code/lab7/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab7/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab7/boot/bootasm.S b/code/lab7/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab7/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab7/boot/bootmain.c b/code/lab7/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab7/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab7/kern/debug/assert.h b/code/lab7/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab7/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab7/kern/debug/kdebug.c b/code/lab7/kern/debug/kdebug.c new file mode 100644 index 0000000..fedbf5b --- /dev/null +++ b/code/lab7/kern/debug/kdebug.c @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* user STABS data structure */ +struct userstabdata { + const struct stab *stabs; + const struct stab *stab_end; + const char *stabstr; + const char *stabstr_end; +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + // find the relevant set of stabs + if (addr >= KERNBASE) { + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + } + else { + // user-program linker script, tools/user.ld puts the information about the + // program's stabs (included __STAB_BEGIN__, __STAB_END__, __STABSTR_BEGIN__, + // and __STABSTR_END__) in a structure located at virtual address USTAB. + const struct userstabdata *usd = (struct userstabdata *)USTAB; + + // make sure that debugger (current process) can access this memory + struct mm_struct *mm; + if (current == NULL || (mm = current->mm) == NULL) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)usd, sizeof(struct userstabdata), 0)) { + return -1; + } + + stabs = usd->stabs; + stab_end = usd->stab_end; + stabstr = usd->stabstr; + stabstr_end = usd->stabstr_end; + + // make sure the STABS and string table memory is valid + if (!user_mem_check(mm, (uintptr_t)stabs, (uintptr_t)stab_end - (uintptr_t)stabs, 0)) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)stabstr, stabstr_end - stabstr, 0)) { + return -1; + } + } + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab7/kern/debug/kdebug.h b/code/lab7/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab7/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab7/kern/debug/monitor.c b/code/lab7/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab7/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab7/kern/debug/monitor.h b/code/lab7/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab7/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab7/kern/debug/panic.c b/code/lab7/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab7/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab7/kern/debug/stab.h b/code/lab7/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab7/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab7/kern/driver/clock.c b/code/lab7/kern/driver/clock.c new file mode 100644 index 0000000..4e67c3b --- /dev/null +++ b/code/lab7/kern/driver/clock.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab7/kern/driver/clock.h b/code/lab7/kern/driver/clock.h new file mode 100644 index 0000000..e22f393 --- /dev/null +++ b/code/lab7/kern/driver/clock.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab7/kern/driver/console.c b/code/lab7/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab7/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab7/kern/driver/console.h b/code/lab7/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab7/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab7/kern/driver/ide.c b/code/lab7/kern/driver/ide.c new file mode 100644 index 0000000..271cfa8 --- /dev/null +++ b/code/lab7/kern/driver/ide.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA_DATA 0x00 +#define ISA_ERROR 0x01 +#define ISA_PRECOMP 0x01 +#define ISA_CTRL 0x02 +#define ISA_SECCNT 0x02 +#define ISA_SECTOR 0x03 +#define ISA_CYL_LO 0x04 +#define ISA_CYL_HI 0x05 +#define ISA_SDH 0x06 +#define ISA_COMMAND 0x07 +#define ISA_STATUS 0x07 + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_DRQ 0x08 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_IDENTIFY 0xEC + +#define IDE_IDENT_SECTORS 20 +#define IDE_IDENT_MODEL 54 +#define IDE_IDENT_CAPABILITIES 98 +#define IDE_IDENT_CMDSETS 164 +#define IDE_IDENT_MAX_LBA 120 +#define IDE_IDENT_MAX_LBA_EXT 200 + +#define IO_BASE0 0x1F0 +#define IO_BASE1 0x170 +#define IO_CTRL0 0x3F4 +#define IO_CTRL1 0x374 + +#define MAX_IDE 4 +#define MAX_NSECS 128 +#define MAX_DISK_NSECS 0x10000000U +#define VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) + +static const struct { + unsigned short base; // I/O Base + unsigned short ctrl; // Control Base +} channels[2] = { + {IO_BASE0, IO_CTRL0}, + {IO_BASE1, IO_CTRL1}, +}; + +#define IO_BASE(ideno) (channels[(ideno) >> 1].base) +#define IO_CTRL(ideno) (channels[(ideno) >> 1].ctrl) + +static struct ide_device { + unsigned char valid; // 0 or 1 (If Device Really Exists) + unsigned int sets; // Commend Sets Supported + unsigned int size; // Size in Sectors + unsigned char model[41]; // Model in String +} ide_devices[MAX_IDE]; + +static int +ide_wait_ready(unsigned short iobase, bool check_error) { + int r; + while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY) + /* nothing */; + if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) { + return -1; + } + return 0; +} + +void +ide_init(void) { + static_assert((SECTSIZE % 4) == 0); + unsigned short ideno, iobase; + for (ideno = 0; ideno < MAX_IDE; ideno ++) { + /* assume that no device here */ + ide_devices[ideno].valid = 0; + + iobase = IO_BASE(ideno); + + /* wait device ready */ + ide_wait_ready(iobase, 0); + + /* step1: select drive */ + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4)); + ide_wait_ready(iobase, 0); + + /* step2: send ATA identify command */ + outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); + ide_wait_ready(iobase, 0); + + /* step3: polling */ + if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) { + continue ; + } + + /* device is ok */ + ide_devices[ideno].valid = 1; + + /* read identification space of the device */ + unsigned int buffer[128]; + insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); + + unsigned char *ident = (unsigned char *)buffer; + unsigned int sectors; + unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); + /* device use 48-bits or 28-bits addressing */ + if (cmdsets & (1 << 26)) { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); + } + else { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); + } + ide_devices[ideno].sets = cmdsets; + ide_devices[ideno].size = sectors; + + /* check if supports LBA */ + assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); + + unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; + unsigned int i, length = 40; + for (i = 0; i < length; i += 2) { + model[i] = data[i + 1], model[i + 1] = data[i]; + } + do { + model[i] = '\0'; + } while (i -- > 0 && model[i] == ' '); + + cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model); + } + + // enable ide interrupt + pic_enable(IRQ_IDE1); + pic_enable(IRQ_IDE2); +} + +bool +ide_device_valid(unsigned short ideno) { + return VALID_IDE(ideno); +} + +size_t +ide_device_size(unsigned short ideno) { + if (ide_device_valid(ideno)) { + return ide_devices[ideno].size; + } + return 0; +} + +int +ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_READ); + + int ret = 0; + for (; nsecs > 0; nsecs --, dst += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + insl(iobase, dst, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + +int +ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); + + int ret = 0; + for (; nsecs > 0; nsecs --, src += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + outsl(iobase, src, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + diff --git a/code/lab7/kern/driver/ide.h b/code/lab7/kern/driver/ide.h new file mode 100644 index 0000000..3e3fd21 --- /dev/null +++ b/code/lab7/kern/driver/ide.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_IDE_H__ +#define __KERN_DRIVER_IDE_H__ + +#include + +void ide_init(void); +bool ide_device_valid(unsigned short ideno); +size_t ide_device_size(unsigned short ideno); + +int ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs); +int ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs); + +#endif /* !__KERN_DRIVER_IDE_H__ */ + diff --git a/code/lab7/kern/driver/intr.c b/code/lab7/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab7/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab7/kern/driver/intr.h b/code/lab7/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab7/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab7/kern/driver/kbdreg.h b/code/lab7/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab7/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab7/kern/driver/picirq.c b/code/lab7/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab7/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab7/kern/driver/picirq.h b/code/lab7/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab7/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab7/kern/fs/fs.h b/code/lab7/kern/fs/fs.h new file mode 100644 index 0000000..92c05e7 --- /dev/null +++ b/code/lab7/kern/fs/fs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_FS_H__ +#define __KERN_FS_FS_H__ + +#include + +#define SECTSIZE 512 +#define PAGE_NSECT (PGSIZE / SECTSIZE) + +#define SWAP_DEV_NO 1 + +#endif /* !__KERN_FS_FS_H__ */ + diff --git a/code/lab7/kern/fs/swapfs.c b/code/lab7/kern/fs/swapfs.c new file mode 100644 index 0000000..d9f6090 --- /dev/null +++ b/code/lab7/kern/fs/swapfs.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include + +void +swapfs_init(void) { + static_assert((PGSIZE % SECTSIZE) == 0); + if (!ide_device_valid(SWAP_DEV_NO)) { + panic("swap fs isn't available.\n"); + } + max_swap_offset = ide_device_size(SWAP_DEV_NO) / (PGSIZE / SECTSIZE); +} + +int +swapfs_read(swap_entry_t entry, struct Page *page) { + return ide_read_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + +int +swapfs_write(swap_entry_t entry, struct Page *page) { + return ide_write_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + diff --git a/code/lab7/kern/fs/swapfs.h b/code/lab7/kern/fs/swapfs.h new file mode 100644 index 0000000..d433926 --- /dev/null +++ b/code/lab7/kern/fs/swapfs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_SWAPFS_H__ +#define __KERN_FS_SWAPFS_H__ + +#include +#include + +void swapfs_init(void); +int swapfs_read(swap_entry_t entry, struct Page *page); +int swapfs_write(swap_entry_t entry, struct Page *page); + +#endif /* !__KERN_FS_SWAPFS_H__ */ + diff --git a/code/lab7/kern/init/entry.S b/code/lab7/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab7/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab7/kern/init/init.c b/code/lab7/kern/init/init.c new file mode 100644 index 0000000..3341078 --- /dev/null +++ b/code/lab7/kern/init/init.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + vmm_init(); // init virtual memory management + sched_init(); // init scheduler + proc_init(); // init process table + + ide_init(); // init ide devices + swap_init(); // init swap + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + cpu_idle(); // run idle process +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab7/kern/libs/rb_tree.c b/code/lab7/kern/libs/rb_tree.c new file mode 100644 index 0000000..0a5fcc8 --- /dev/null +++ b/code/lab7/kern/libs/rb_tree.c @@ -0,0 +1,528 @@ +#include +#include +#include +#include +#include +#include + +/* rb_node_create - create a new rb_node */ +static inline rb_node * +rb_node_create(void) { + return kmalloc(sizeof(rb_node)); +} + +/* rb_tree_empty - tests if tree is empty */ +static inline bool +rb_tree_empty(rb_tree *tree) { + rb_node *nil = tree->nil, *root = tree->root; + return root->left == nil; +} + +/* * + * rb_tree_create - creates a new red-black tree, the 'compare' function + * is required and returns 'NULL' if failed. + * + * Note that, root->left should always point to the node that is the root + * of the tree. And nil points to a 'NULL' node which should always be + * black and may have arbitrary children and parent node. + * */ +rb_tree * +rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)) { + assert(compare != NULL); + + rb_tree *tree; + rb_node *nil, *root; + + if ((tree = kmalloc(sizeof(rb_tree))) == NULL) { + goto bad_tree; + } + + tree->compare = compare; + + if ((nil = rb_node_create()) == NULL) { + goto bad_node_cleanup_tree; + } + + nil->parent = nil->left = nil->right = nil; + nil->red = 0; + tree->nil = nil; + + if ((root = rb_node_create()) == NULL) { + goto bad_node_cleanup_nil; + } + + root->parent = root->left = root->right = nil; + root->red = 0; + tree->root = root; + return tree; + +bad_node_cleanup_nil: + kfree(nil); +bad_node_cleanup_tree: + kfree(tree); +bad_tree: + return NULL; +} + +/* * + * FUNC_ROTATE - rotates as described in "Introduction to Algorithm". + * + * For example, FUNC_ROTATE(rb_left_rotate, left, right) can be expaned to a + * left-rotate function, which requires an red-black 'tree' and a node 'x' + * to be rotated on. Basically, this function, named rb_left_rotate, makes the + * parent of 'x' be the left child of 'x', 'x' the parent of its parent before + * rotation and finally fixes other nodes accordingly. + * + * FUNC_ROTATE(xx, left, right) means left-rotate, + * and FUNC_ROTATE(xx, right, left) means right-rotate. + * */ +#define FUNC_ROTATE(func_name, _left, _right) \ +static void \ +func_name(rb_tree *tree, rb_node *x) { \ + rb_node *nil = tree->nil, *y = x->_right; \ + assert(x != tree->root && x != nil && y != nil); \ + x->_right = y->_left; \ + if (y->_left != nil) { \ + y->_left->parent = x; \ + } \ + y->parent = x->parent; \ + if (x == x->parent->_left) { \ + x->parent->_left = y; \ + } \ + else { \ + x->parent->_right = y; \ + } \ + y->_left = x; \ + x->parent = y; \ + assert(!(nil->red)); \ +} + +FUNC_ROTATE(rb_left_rotate, left, right); +FUNC_ROTATE(rb_right_rotate, right, left); + +#undef FUNC_ROTATE + +#define COMPARE(tree, node1, node2) \ + ((tree))->compare((node1), (node2)) + +/* * + * rb_insert_binary - insert @node to red-black @tree as if it were + * a regular binary tree. This function is only intended to be called + * by function rb_insert. + * */ +static inline void +rb_insert_binary(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node, *nil = tree->nil, *root = tree->root; + + z->left = z->right = nil; + y = root, x = y->left; + while (x != nil) { + y = x; + x = (COMPARE(tree, x, node) > 0) ? x->left : x->right; + } + z->parent = y; + if (y == root || COMPARE(tree, y, z) > 0) { + y->left = z; + } + else { + y->right = z; + } +} + +/* rb_insert - insert a node to red-black tree */ +void +rb_insert(rb_tree *tree, rb_node *node) { + rb_insert_binary(tree, node); + node->red = 1; + + rb_node *x = node, *y; + +#define RB_INSERT_SUB(_left, _right) \ + do { \ + y = x->parent->parent->_right; \ + if (y->red) { \ + x->parent->red = 0; \ + y->red = 0; \ + x->parent->parent->red = 1; \ + x = x->parent->parent; \ + } \ + else { \ + if (x == x->parent->_right) { \ + x = x->parent; \ + rb_##_left##_rotate(tree, x); \ + } \ + x->parent->red = 0; \ + x->parent->parent->red = 1; \ + rb_##_right##_rotate(tree, x->parent->parent); \ + } \ + } while (0) + + while (x->parent->red) { + if (x->parent == x->parent->parent->left) { + RB_INSERT_SUB(left, right); + } + else { + RB_INSERT_SUB(right, left); + } + } + tree->root->left->red = 0; + assert(!(tree->nil->red) && !(tree->root->red)); + +#undef RB_INSERT_SUB +} + +/* * + * rb_tree_successor - returns the successor of @node, or nil + * if no successor exists. Make sure that @node must belong to @tree, + * and this function should only be called by rb_node_prev. + * */ +static inline rb_node * +rb_tree_successor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->right) != nil) { + while (y->left != nil) { + y = y->left; + } + return y; + } + else { + y = x->parent; + while (x == y->right) { + x = y, y = y->parent; + } + if (y == tree->root) { + return nil; + } + return y; + } +} + +/* * + * rb_tree_predecessor - returns the predecessor of @node, or nil + * if no predecessor exists, likes rb_tree_successor. + * */ +static inline rb_node * +rb_tree_predecessor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->left) != nil) { + while (y->right != nil) { + y = y->right; + } + return y; + } + else { + y = x->parent; + while (x == y->left) { + if (y == tree->root) { + return nil; + } + x = y, y = y->parent; + } + return y; + } +} + +/* * + * rb_search - returns a node with value 'equal' to @key (according to + * function @compare). If there're multiple nodes with value 'equal' to @key, + * the functions returns the one highest in the tree. + * */ +rb_node * +rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key) { + rb_node *nil = tree->nil, *node = tree->root->left; + int r; + while (node != nil && (r = compare(node, key)) != 0) { + node = (r > 0) ? node->left : node->right; + } + return (node != nil) ? node : NULL; +} + +/* * + * rb_delete_fixup - performs rotations and changes colors to restore + * red-black properties after a node is deleted. + * */ +static void +rb_delete_fixup(rb_tree *tree, rb_node *node) { + rb_node *x = node, *w, *root = tree->root->left; + +#define RB_DELETE_FIXUP_SUB(_left, _right) \ + do { \ + w = x->parent->_right; \ + if (w->red) { \ + w->red = 0; \ + x->parent->red = 1; \ + rb_##_left##_rotate(tree, x->parent); \ + w = x->parent->_right; \ + } \ + if (!w->_left->red && !w->_right->red) { \ + w->red = 1; \ + x = x->parent; \ + } \ + else { \ + if (!w->_right->red) { \ + w->_left->red = 0; \ + w->red = 1; \ + rb_##_right##_rotate(tree, w); \ + w = x->parent->_right; \ + } \ + w->red = x->parent->red; \ + x->parent->red = 0; \ + w->_right->red = 0; \ + rb_##_left##_rotate(tree, x->parent); \ + x = root; \ + } \ + } while (0) + + while (x != root && !x->red) { + if (x == x->parent->left) { + RB_DELETE_FIXUP_SUB(left, right); + } + else { + RB_DELETE_FIXUP_SUB(right, left); + } + } + x->red = 0; + +#undef RB_DELETE_FIXUP_SUB +} + +/* * + * rb_delete - deletes @node from @tree, and calls rb_delete_fixup to + * restore red-black properties. + * */ +void +rb_delete(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node; + rb_node *nil = tree->nil, *root = tree->root; + + y = (z->left == nil || z->right == nil) ? z : rb_tree_successor(tree, z); + x = (y->left != nil) ? y->left : y->right; + + assert(y != root && y != nil); + + x->parent = y->parent; + if (y == y->parent->left) { + y->parent->left = x; + } + else { + y->parent->right = x; + } + + bool need_fixup = !(y->red); + + if (y != z) { + if (z == z->parent->left) { + z->parent->left = y; + } + else { + z->parent->right = y; + } + z->left->parent = z->right->parent = y; + *y = *z; + } + if (need_fixup) { + rb_delete_fixup(tree, x); + } +} + +/* rb_tree_destroy - destroy a tree and free memory */ +void +rb_tree_destroy(rb_tree *tree) { + kfree(tree->root); + kfree(tree->nil); + kfree(tree); +} + +/* * + * rb_node_prev - returns the predecessor node of @node in @tree, + * or 'NULL' if no predecessor exists. + * */ +rb_node * +rb_node_prev(rb_tree *tree, rb_node *node) { + rb_node *prev = rb_tree_predecessor(tree, node); + return (prev != tree->nil) ? prev : NULL; +} + +/* * + * rb_node_next - returns the successor node of @node in @tree, + * or 'NULL' if no successor exists. + * */ +rb_node * +rb_node_next(rb_tree *tree, rb_node *node) { + rb_node *next = rb_tree_successor(tree, node); + return (next != tree->nil) ? next : NULL; +} + +/* rb_node_root - returns the root node of a @tree, or 'NULL' if tree is empty */ +rb_node * +rb_node_root(rb_tree *tree) { + rb_node *node = tree->root->left; + return (node != tree->nil) ? node : NULL; +} + +/* rb_node_left - gets the left child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_left(rb_tree *tree, rb_node *node) { + rb_node *left = node->left; + return (left != tree->nil) ? left : NULL; +} + +/* rb_node_right - gets the right child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_right(rb_tree *tree, rb_node *node) { + rb_node *right = node->right; + return (right != tree->nil) ? right : NULL; +} + +int +check_tree(rb_tree *tree, rb_node *node) { + rb_node *nil = tree->nil; + if (node == nil) { + assert(!node->red); + return 1; + } + if (node->left != nil) { + assert(COMPARE(tree, node, node->left) >= 0); + assert(node->left->parent == node); + } + if (node->right != nil) { + assert(COMPARE(tree, node, node->right) <= 0); + assert(node->right->parent == node); + } + if (node->red) { + assert(!node->left->red && !node->right->red); + } + int hb_left = check_tree(tree, node->left); + int hb_right = check_tree(tree, node->right); + assert(hb_left == hb_right); + int hb = hb_left; + if (!node->red) { + hb ++; + } + return hb; +} + +static void * +check_safe_kmalloc(size_t size) { + void *ret = kmalloc(size); + assert(ret != NULL); + return ret; +} + +struct check_data { + long data; + rb_node rb_link; +}; + +#define rbn2data(node) \ + (to_struct(node, struct check_data, rb_link)) + +static inline int +check_compare1(rb_node *node1, rb_node *node2) { + return rbn2data(node1)->data - rbn2data(node2)->data; +} + +static inline int +check_compare2(rb_node *node, void *key) { + return rbn2data(node)->data - (long)key; +} + +void +check_rb_tree(void) { + rb_tree *tree = rb_tree_create(check_compare1); + assert(tree != NULL); + + rb_node *nil = tree->nil, *root = tree->root; + assert(!nil->red && root->left == nil); + + int total = 1000; + struct check_data **all = check_safe_kmalloc(sizeof(struct check_data *) * total); + + long i; + for (i = 0; i < total; i ++) { + all[i] = check_safe_kmalloc(sizeof(struct check_data)); + all[i]->data = i; + } + + int *mark = check_safe_kmalloc(sizeof(int) * total); + memset(mark, 0, sizeof(int) * total); + + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + int j = (rand() % (total - i)) + i; + struct check_data *z = all[i]; + all[i] = all[j]; + all[j] = z; + } + + memset(mark, 0, sizeof(int) * total); + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_node *node; + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)(all[i]->data)); + assert(node != NULL && node == &(all[i]->rb_link)); + } + + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)i); + assert(node != NULL && rbn2data(node)->data == i); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(!nil->red && root->left == nil); + + long max = 32; + if (max > total) { + max = total; + } + + for (i = 0; i < max; i ++) { + all[i]->data = max; + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + for (i = 0; i < max; i ++) { + node = rb_search(tree, check_compare2, (void *)max); + assert(node != NULL && rbn2data(node)->data == max); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(rb_tree_empty(tree)); + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_tree_destroy(tree); + + for (i = 0; i < total; i ++) { + kfree(all[i]); + } + + kfree(mark); + kfree(all); +} + diff --git a/code/lab7/kern/libs/rb_tree.h b/code/lab7/kern/libs/rb_tree.h new file mode 100644 index 0000000..a2aa9aa --- /dev/null +++ b/code/lab7/kern/libs/rb_tree.h @@ -0,0 +1,32 @@ +#ifndef __KERN_LIBS_RB_TREE_H__ +#define __KERN_LIBS_RB_TREE_H__ + +#include + +typedef struct rb_node { + bool red; // if red = 0, it's a black node + struct rb_node *parent; + struct rb_node *left, *right; +} rb_node; + +typedef struct rb_tree { + // compare function should return -1 if *node1 < *node2, 1 if *node1 > *node2, and 0 otherwise + int (*compare)(rb_node *node1, rb_node *node2); + struct rb_node *nil, *root; +} rb_tree; + +rb_tree *rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)); +void rb_tree_destroy(rb_tree *tree); +void rb_insert(rb_tree *tree, rb_node *node); +void rb_delete(rb_tree *tree, rb_node *node); +rb_node *rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key); +rb_node *rb_node_prev(rb_tree *tree, rb_node *node); +rb_node *rb_node_next(rb_tree *tree, rb_node *node); +rb_node *rb_node_root(rb_tree *tree); +rb_node *rb_node_left(rb_tree *tree, rb_node *node); +rb_node *rb_node_right(rb_tree *tree, rb_node *node); + +void check_rb_tree(void); + +#endif /* !__KERN_LIBS_RBTREE_H__ */ + diff --git a/code/lab7/kern/libs/readline.c b/code/lab7/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab7/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab7/kern/libs/stdio.c b/code/lab7/kern/libs/stdio.c new file mode 100644 index 0000000..5efefcd --- /dev/null +++ b/code/lab7/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include + +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab7/kern/mm/default_pmm.c b/code/lab7/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab7/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab7/kern/mm/default_pmm.h b/code/lab7/kern/mm/default_pmm.h new file mode 100644 index 0000000..5f4e6fc --- /dev/null +++ b/code/lab7/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; +extern free_area_t free_area; +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab7/kern/mm/kmalloc.c b/code/lab7/kern/mm/kmalloc.c new file mode 100644 index 0000000..aa5bb90 --- /dev/null +++ b/code/lab7/kern/mm/kmalloc.c @@ -0,0 +1,640 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The slab allocator used in ucore is based on an algorithm first introduced by + Jeff Bonwick for the SunOS operating system. The paper can be download from + http://citeseer.ist.psu.edu/bonwick94slab.html + An implementation of the Slab Allocator as described in outline in; + UNIX Internals: The New Frontiers by Uresh Vahalia + Pub: Prentice Hall ISBN 0-13-101908-2 + Within a kernel, a considerable amount of memory is allocated for a finite set + of objects such as file descriptors and other common structures. Jeff found that + the amount of time required to initialize a regular object in the kernel exceeded + the amount of time required to allocate and deallocate it. His conclusion was + that instead of freeing the memory back to a global pool, he would have the memory + remain initialized for its intended purpose. + In our simple slab implementation, the the high-level organization of the slab + structures is simplied. At the highest level is an array slab_cache[SLAB_CACHE_NUM], + and each array element is a slab_cache which has slab chains. Each slab_cache has + two list, one list chains the full allocated slab, and another list chains the notfull + allocated(maybe empty) slab. And each slab has fixed number(2^n) of pages. In each + slab, there are a lot of objects (such as ) with same fixed size(32B ~ 128KB). + + +----------------------------------+ + | slab_cache[0] for 0~32B obj | + +----------------------------------+ + | slab_cache[1] for 33B~64B obj |-->lists for slabs + +----------------------------------+ | + | slab_cache[2] for 65B~128B obj | | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + +----------------------------------+ | + | slab_cache[12]for 64KB~128KB obj | | + +----------------------------------+ | + | + slabs_full/slabs_not +---------------------+ + -<-----------<----------<-+ + | | | + slab1 slab2 slab3... + | + |-------|-------| + pages1 pages2 pages3... + | + | + | + slab_t+n*bufctl_t+obj1-obj2-obj3...objn (the size of obj is small) + | + OR + | + obj1-obj2-obj3...objn WITH slab_t+n*bufctl_t in another slab (the size of obj is BIG) + + The important functions are: + kmem_cache_grow(kmem_cache_t *cachep) + kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) + kmalloc(size_t size): used by outside functions need dynamicly get memory + kfree(void *objp): used by outside functions need dynamicly release memory +*/ + +#define BUFCTL_END 0xFFFFFFFFL // the signature of the last bufctl +#define SLAB_LIMIT 0xFFFFFFFEL // the max value of obj number + +typedef size_t kmem_bufctl_t; //the index of obj in slab + +typedef struct slab_s { + list_entry_t slab_link; // the list entry linked to kmem_cache list + void *s_mem; // the kernel virtual address of the first obj in slab + size_t inuse; // the number of allocated objs + size_t offset; // the first obj's offset value in slab + kmem_bufctl_t free; // the first free obj's index in slab +} slab_t; + +// get the slab address according to the link element (see list.h) +#define le2slab(le, member) \ + to_struct((le), slab_t, member) + +typedef struct kmem_cache_s kmem_cache_t; + + +struct kmem_cache_s { + list_entry_t slabs_full; // list for fully allocated slabs + list_entry_t slabs_notfull; // list for not-fully allocated slabs + + size_t objsize; // the fixed size of obj + size_t num; // number of objs per slab + size_t offset; // this first obj's offset in slab + bool off_slab; // the control part of slab in slab or not. + + /* order of pages per slab (2^n) */ + size_t page_order; + + kmem_cache_t *slab_cachep; +}; + +#define MIN_SIZE_ORDER 5 // 32 +#define MAX_SIZE_ORDER 17 // 128k +#define SLAB_CACHE_NUM (MAX_SIZE_ORDER - MIN_SIZE_ORDER + 1) + +static kmem_cache_t slab_cache[SLAB_CACHE_NUM]; + +static void init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align); +static void check_slab(void); + + +//slab_init - call init_kmem_cache function to reset the slab_cache array +static void +slab_init(void) { + size_t i; + //the align bit for obj in slab. 2^n could be better for performance + size_t align = 16; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + init_kmem_cache(slab_cache + i, 1 << (i + MIN_SIZE_ORDER), align); + } + check_slab(); +} + +inline void +kmalloc_init(void) { + slab_init(); + cprintf("kmalloc_init() succeeded!\n"); +} + +//slab_allocated - summary the total size of allocated objs +static size_t +slab_allocated(void) { + size_t total = 0; + int i; + bool intr_flag; + local_intr_save(intr_flag); + { + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + list_entry_t *list, *le; + list = le = &(cachep->slabs_full); + while ((le = list_next(le)) != list) { + total += cachep->num * cachep->objsize; + } + list = le = &(cachep->slabs_notfull); + while ((le = list_next(le)) != list) { + slab_t *slabp = le2slab(le, slab_link); + total += slabp->inuse * cachep->objsize; + } + } + } + local_intr_restore(intr_flag); + return total; +} + +inline size_t +kallocated(void) { + return slab_allocated(); +} + +// slab_mgmt_size - get the size of slab control area (slab_t+num*kmem_bufctl_t) +static size_t +slab_mgmt_size(size_t num, size_t align) { + return ROUNDUP(sizeof(slab_t) + num * sizeof(kmem_bufctl_t), align); +} + +// cacahe_estimate - estimate the number of objs in a slab +static void +cache_estimate(size_t order, size_t objsize, size_t align, bool off_slab, size_t *remainder, size_t *num) { + size_t nr_objs, mgmt_size; + size_t slab_size = (PGSIZE << order); + + if (off_slab) { + mgmt_size = 0; + nr_objs = slab_size / objsize; + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + } + else { + nr_objs = (slab_size - sizeof(slab_t)) / (objsize + sizeof(kmem_bufctl_t)); + while (slab_mgmt_size(nr_objs, align) + nr_objs * objsize > slab_size) { + nr_objs --; + } + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + mgmt_size = slab_mgmt_size(nr_objs, align); + } + *num = nr_objs; + *remainder = slab_size - nr_objs * objsize - mgmt_size; +} + +// calculate_slab_order - estimate the size(4K~4M) of slab +// paramemters: +// cachep: the slab_cache +// objsize: the size of obj +// align: align bit for objs +// off_slab: the control part of slab in slab or not +// left_over: the size of can not be used area in slab +static void +calculate_slab_order(kmem_cache_t *cachep, size_t objsize, size_t align, bool off_slab, size_t *left_over) { + size_t order; + for (order = 0; order <= KMALLOC_MAX_ORDER; order ++) { + size_t num, remainder; + cache_estimate(order, objsize, align, off_slab, &remainder, &num); + if (num != 0) { + if (off_slab) { + size_t off_slab_limit = objsize - sizeof(slab_t); + off_slab_limit /= sizeof(kmem_bufctl_t); + if (num > off_slab_limit) { + panic("off_slab: objsize = %d, num = %d.", objsize, num); + } + } + if (remainder * 8 <= (PGSIZE << order)) { + cachep->num = num; + cachep->page_order = order; + if (left_over != NULL) { + *left_over = remainder; + } + return ; + } + } + } + panic("calculate_slab_over: failed."); +} + +// getorder - find order, should satisfy n <= minest 2^order +static inline size_t +getorder(size_t n) { + size_t order = MIN_SIZE_ORDER, order_size = (1 << order); + for (; order <= MAX_SIZE_ORDER; order ++, order_size <<= 1) { + if (n <= order_size) { + return order; + } + } + panic("getorder failed. %d\n", n); +} + +// init_kmem_cache - initial a slab_cache cachep according to the obj with the size = objsize +static void +init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align) { + list_init(&(cachep->slabs_full)); + list_init(&(cachep->slabs_notfull)); + + objsize = ROUNDUP(objsize, align); + cachep->objsize = objsize; + cachep->off_slab = (objsize >= (PGSIZE >> 3)); + + size_t left_over; + calculate_slab_order(cachep, objsize, align, cachep->off_slab, &left_over); + + assert(cachep->num > 0); + + size_t mgmt_size = slab_mgmt_size(cachep->num, align); + + if (cachep->off_slab && left_over >= mgmt_size) { + cachep->off_slab = 0; + } + + if (cachep->off_slab) { + cachep->offset = 0; + cachep->slab_cachep = slab_cache + (getorder(mgmt_size) - MIN_SIZE_ORDER); + } + else { + cachep->offset = mgmt_size; + } +} + +static void *kmem_cache_alloc(kmem_cache_t *cachep); + +#define slab_bufctl(slabp) \ + ((kmem_bufctl_t*)(((slab_t *)(slabp)) + 1)) + +// kmem_cache_slabmgmt - get the address of a slab according to page +// - and initialize the slab according to cachep +static slab_t * +kmem_cache_slabmgmt(kmem_cache_t *cachep, struct Page *page) { + void *objp = page2kva(page); + slab_t *slabp; + if (cachep->off_slab) { + if ((slabp = kmem_cache_alloc(cachep->slab_cachep)) == NULL) { + return NULL; + } + } + else { + slabp = page2kva(page); + } + slabp->inuse = 0; + slabp->offset = cachep->offset; + slabp->s_mem = objp + cachep->offset; + return slabp; +} + +#define SET_PAGE_CACHE(page, cachep) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + kmem_cache_t **__cachepp = (kmem_cache_t **)&(__page->page_link.next); \ + *__cachepp = (kmem_cache_t *)(cachep); \ + } while (0) + +#define SET_PAGE_SLAB(page, slabp) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + slab_t **__cachepp = (slab_t **)&(__page->page_link.prev); \ + *__cachepp = (slab_t *)(slabp); \ + } while (0) + +// kmem_cache_grow - allocate a new slab by calling alloc_pages +// - set control area in the new slab +static bool +kmem_cache_grow(kmem_cache_t *cachep) { + struct Page *page = alloc_pages(1 << cachep->page_order); + if (page == NULL) { + goto failed; + } + + slab_t *slabp; + if ((slabp = kmem_cache_slabmgmt(cachep, page)) == NULL) { + goto oops; + } + + size_t order_size = (1 << cachep->page_order); + do { + //setup this page in the free list (see memlayout.h: struct page)??? + SET_PAGE_CACHE(page, cachep); + SET_PAGE_SLAB(page, slabp); + //this page is used for slab + SetPageSlab(page); + page ++; + } while (-- order_size); + + int i; + for (i = 0; i < cachep->num; i ++) { + slab_bufctl(slabp)[i] = i + 1; + } + slab_bufctl(slabp)[cachep->num - 1] = BUFCTL_END; + slabp->free = 0; + + bool intr_flag; + local_intr_save(intr_flag); + { + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } + local_intr_restore(intr_flag); + return 1; + +oops: + free_pages(page, 1 << cachep->page_order); +failed: + return 0; +} + +// kmem_cache_alloc_one - allocate a obj in a slab +static void * +kmem_cache_alloc_one(kmem_cache_t *cachep, slab_t *slabp) { + slabp->inuse ++; + void *objp = slabp->s_mem + slabp->free * cachep->objsize; + slabp->free = slab_bufctl(slabp)[slabp->free]; + + if (slabp->free == BUFCTL_END) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_full), &(slabp->slab_link)); + } + return objp; +} + +// kmem_cache_alloc - call kmem_cache_alloc_one function to allocate a obj +// - if no free obj, try to allocate a slab +static void * +kmem_cache_alloc(kmem_cache_t *cachep) { + void *objp; + bool intr_flag; + +try_again: + local_intr_save(intr_flag); + if (list_empty(&(cachep->slabs_notfull))) { + goto alloc_new_slab; + } + slab_t *slabp = le2slab(list_next(&(cachep->slabs_notfull)), slab_link); + objp = kmem_cache_alloc_one(cachep, slabp); + local_intr_restore(intr_flag); + return objp; + +alloc_new_slab: + local_intr_restore(intr_flag); + + if (kmem_cache_grow(cachep)) { + goto try_again; + } + return NULL; +} + +// kmalloc - simple interface used by outside functions +// - to allocate a free memory using kmem_cache_alloc function +void * +kmalloc(size_t size) { + assert(size > 0); + size_t order = getorder(size); + if (order > MAX_SIZE_ORDER) { + return NULL; + } + return kmem_cache_alloc(slab_cache + (order - MIN_SIZE_ORDER)); +} + +static void kmem_cache_free(kmem_cache_t *cachep, void *obj); + +// kmem_slab_destroy - call free_pages & kmem_cache_free to free a slab +static void +kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) { + struct Page *page = kva2page(slabp->s_mem - slabp->offset); + + struct Page *p = page; + size_t order_size = (1 << cachep->page_order); + do { + assert(PageSlab(p)); + ClearPageSlab(p); + p ++; + } while (-- order_size); + + free_pages(page, 1 << cachep->page_order); + + if (cachep->off_slab) { + kmem_cache_free(cachep->slab_cachep, slabp); + } +} + +// kmem_cache_free_one - free an obj in a slab +// - if slab->inuse==0, then free the slab +static void +kmem_cache_free_one(kmem_cache_t *cachep, slab_t *slabp, void *objp) { + //should not use divide operator ??? + size_t objnr = (objp - slabp->s_mem) / cachep->objsize; + slab_bufctl(slabp)[objnr] = slabp->free; + slabp->free = objnr; + + slabp->inuse --; + + if (slabp->inuse == 0) { + list_del(&(slabp->slab_link)); + kmem_slab_destroy(cachep, slabp); + } + else if (slabp->inuse == cachep->num -1 ) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } +} + +#define GET_PAGE_CACHE(page) \ + (kmem_cache_t *)((page)->page_link.next) + +#define GET_PAGE_SLAB(page) \ + (slab_t *)((page)->page_link.prev) + +// kmem_cache_free - call kmem_cache_free_one function to free an obj +static void +kmem_cache_free(kmem_cache_t *cachep, void *objp) { + bool intr_flag; + struct Page *page = kva2page(objp); + + if (!PageSlab(page)) { + panic("not a slab page %08x\n", objp); + } + local_intr_save(intr_flag); + { + kmem_cache_free_one(cachep, GET_PAGE_SLAB(page), objp); + } + local_intr_restore(intr_flag); +} + +// kfree - simple interface used by ooutside functions to free an obj +void +kfree(void *objp) { + kmem_cache_free(GET_PAGE_CACHE(kva2page(objp)), objp); +} + +static inline void +check_slab_empty(void) { + int i; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + assert(list_empty(&(cachep->slabs_full))); + assert(list_empty(&(cachep->slabs_notfull))); + } +} + +void +check_slab(void) { + int i; + void *v0, *v1; + + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = slab_allocated(); + + /* slab must be empty now */ + check_slab_empty(); + assert(slab_allocated() == 0); + + kmem_cache_t *cachep0, *cachep1; + + cachep0 = slab_cache; + assert(cachep0->objsize == 32 && cachep0->num > 1 && !cachep0->off_slab); + assert((v0 = kmalloc(16)) != NULL); + + slab_t *slabp0, *slabp1; + + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + assert(slabp0->inuse == 1 && list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + struct Page *p0, *p1; + size_t order_size; + + + p0 = kva2page(slabp0->s_mem - slabp0->offset), p1 = p0; + order_size = (1 << cachep0->page_order); + for (i = 0; i < cachep0->page_order; i ++, p1 ++) { + assert(PageSlab(p1)); + assert(GET_PAGE_CACHE(p1) == cachep0 && GET_PAGE_SLAB(p1) == slabp0); + } + + assert(v0 == slabp0->s_mem); + assert((v1 = kmalloc(16)) != NULL && v1 == v0 + 32); + + kfree(v0); + assert(slabp0->free == 0); + kfree(v1); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->page_order; i ++, p0 ++) { + assert(!PageSlab(p0)); + } + + + v0 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + for (i = 0; i < cachep0->num - 1; i ++) { + kmalloc(16); + } + + assert(slabp0->inuse == cachep0->num); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + v1 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + kfree(v0); + assert(list_empty(&(cachep0->slabs_full))); + assert(list_next(&(slabp0->slab_link)) == &(slabp1->slab_link) + || list_next(&(slabp1->slab_link)) == &(slabp0->slab_link)); + + kfree(v1); + assert(!list_empty(&(cachep0->slabs_notfull))); + assert(list_next(&(cachep0->slabs_notfull)) == &(slabp0->slab_link)); + assert(list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + v1 = kmalloc(16); + assert(v1 == v0); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->num; i ++) { + kfree(v1 + i * cachep0->objsize); + } + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + cachep0 = slab_cache; + + bool has_off_slab = 0; + for (i = 0; i < SLAB_CACHE_NUM; i ++, cachep0 ++) { + if (cachep0->off_slab) { + has_off_slab = 1; + cachep1 = cachep0->slab_cachep; + if (!cachep1->off_slab) { + break; + } + } + } + + if (!has_off_slab) { + goto check_pass; + } + + assert(cachep0->off_slab && !cachep1->off_slab); + assert(cachep1 < cachep0); + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + assert(list_empty(&(cachep1->slabs_full))); + assert(list_empty(&(cachep1->slabs_notfull))); + + v0 = kmalloc(cachep0->objsize); + p0 = kva2page(v0); + assert(page2kva(p0) == v0); + + if (cachep0->num == 1) { + assert(!list_empty(&(cachep0->slabs_full))); + slabp0 = le2slab(list_next(&(cachep0->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + } + + assert(slabp0 != NULL); + + if (cachep1->num == 1) { + assert(!list_empty(&(cachep1->slabs_full))); + slabp1 = le2slab(list_next(&(cachep1->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep1->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep1->slabs_notfull)), slab_link); + } + + assert(slabp1 != NULL); + + order_size = (1 << cachep0->page_order); + for (i = 0; i < order_size; i ++, p0 ++) { + assert(PageSlab(p0)); + assert(GET_PAGE_CACHE(p0) == cachep0 && GET_PAGE_SLAB(p0) == slabp0); + } + + kfree(v0); + +check_pass: + + check_rb_tree(); + check_slab_empty(); + assert(slab_allocated() == 0); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == slab_allocated()); + + cprintf("check_slab() succeeded!\n"); +} + diff --git a/code/lab7/kern/mm/kmalloc.h b/code/lab7/kern/mm/kmalloc.h new file mode 100644 index 0000000..8617975 --- /dev/null +++ b/code/lab7/kern/mm/kmalloc.h @@ -0,0 +1,16 @@ +#ifndef __KERN_MM_SLAB_H__ +#define __KERN_MM_SLAB_H__ + +#include + +#define KMALLOC_MAX_ORDER 10 + +void kmalloc_init(void); + +void *kmalloc(size_t n); +void kfree(void *objp); + +size_t kallocated(void); + +#endif /* !__KERN_MM_SLAB_H__ */ + diff --git a/code/lab7/kern/mm/memlayout.h b/code/lab7/kern/mm/memlayout.h new file mode 100644 index 0000000..d84bf93 --- /dev/null +++ b/code/lab7/kern/mm/memlayout.h @@ -0,0 +1,169 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | Invalid Memory (*) | --/-- + * USERTOP -------------> +---------------------------------+ 0xB0000000 + * | User stack | + * +---------------------------------+ + * | | + * : : + * | ~~~~~~~~~~~~~~~~ | + * : : + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | User Program & Heap | + * UTEXT ---------------> +---------------------------------+ 0x00800000 + * | Invalid Memory (*) | --/-- + * | - - - - - - - - - - - - - - - | + * | User STAB Data (optional) | + * USERBASE, USTAB------> +---------------------------------+ 0x00200000 + * | Invalid Memory (*) | --/-- + * 0 -------------------> +---------------------------------+ 0x00000000 + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#define USERTOP 0xB0000000 +#define USTACKTOP USERTOP +#define USTACKPAGE 256 // # of pages in user stack +#define USTACKSIZE (USTACKPAGE * PGSIZE) // sizeof user stack + +#define USERBASE 0x00200000 +#define UTEXT 0x00800000 // where user programs generally begin +#define USTAB USERBASE // the location of the user STABS data structure + +#define USER_ACCESS(start, end) \ +(USERBASE <= (start) && (start) < (end) && (end) <= USERTOP) + +#define KERN_ACCESS(start, end) \ +(KERNBASE <= (start) && (start) < (end) && (end) <= KERNTOP) + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; +typedef pte_t swap_entry_t; //the pte can also be a swap entry + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // used in buddy system, stores the order (the X in 2^X) of the continuous memory block + int zone_num; // used in buddy system, the No. of zone which the page belongs to + list_entry_t page_link; // free list link + list_entry_t pra_page_link; // used for pra (page replace algorithm) + uintptr_t pra_vaddr; // used for pra (page replace algorithm) +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +/* for slab style kmalloc */ +#define PG_slab 2 // page frame is included in a slab +#define SetPageSlab(page) set_bit(PG_slab, &((page)->flags)) +#define ClearPageSlab(page) clear_bit(PG_slab, &((page)->flags)) +#define PageSlab(page) test_bit(PG_slab, &((page)->flags)) + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab7/kern/mm/mmu.h b/code/lab7/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab7/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab7/kern/mm/pmm.c b/code/lab7/kern/mm/pmm.c new file mode 100644 index 0000000..cc3f28c --- /dev/null +++ b/code/lab7/kern/mm/pmm.c @@ -0,0 +1,759 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + + while (1) + { + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + + if (page != NULL || n > 1 || swap_init_ok == 0) break; + + extern struct mm_struct *check_mm_struct; + //cprintf("page %x, call swap_out in alloc_pages %d\n",page, n); + swap_out(check_mm_struct, n, 0); + } + //cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages)); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + + kmalloc_init(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +void +unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + do { + pte_t *ptep = get_pte(pgdir, start, 0); + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + if (*ptep != 0) { + page_remove_pte(pgdir, start, ptep); + } + start += PGSIZE; + } while (start != 0 && start < end); +} + +void +exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + start = ROUNDDOWN(start, PTSIZE); + do { + int pde_idx = PDX(start); + if (pgdir[pde_idx] & PTE_P) { + free_page(pde2page(pgdir[pde_idx])); + pgdir[pde_idx] = 0; + } + start += PTSIZE; + } while (start != 0 && start < end); +} +/* copy_range - copy content of memory (start, end) of one process A to another process B + * @to: the addr of process B's Page Directory + * @from: the addr of process A's Page Directory + * @share: flags to indicate to dup OR share. We just use dup method, so it didn't be used. + * + * CALL GRAPH: copy_mm-->dup_mmap-->copy_range + */ +int +copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + // copy content by page unit. + do { + //call get_pte to find process A's pte according to the addr start + pte_t *ptep = get_pte(from, start, 0), *nptep; + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + //call get_pte to find process B's pte according to the addr start. If pte is NULL, just alloc a PT + if (*ptep & PTE_P) { + if ((nptep = get_pte(to, start, 1)) == NULL) { + return -E_NO_MEM; + } + uint32_t perm = (*ptep & PTE_USER); + //get page from ptep + struct Page *page = pte2page(*ptep); + // alloc a page for process B + struct Page *npage=alloc_page(); + assert(page!=NULL); + assert(npage!=NULL); + int ret=0; + /* LAB5:EXERCISE2 YOUR CODE + * replicate content of page to npage, build the map of phy addr of nage with the linear addr start + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h) + * page_insert: build the map of phy addr of an Page with the linear addr la + * memcpy: typical memory copy function + * + * (1) find src_kvaddr: the kernel virtual address of page + * (2) find dst_kvaddr: the kernel virtual address of npage + * (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE + * (4) build the map of phy addr of nage with the linear addr start + */ + assert(ret == 0); + } + start += PGSIZE; + } while (start != 0 && start < end); + return 0; +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +// pgdir_alloc_page - call alloc_page & page_insert functions to +// - allocate a page size memory & setup an addr map +// - pa<->la with linear address la and the PDT pgdir +struct Page * +pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { + struct Page *page = alloc_page(); + if (page != NULL) { + if (page_insert(pgdir, page, la, perm) != 0) { + free_page(page); + return NULL; + } + if (swap_init_ok){ + if(check_mm_struct!=NULL) { + swap_map_swappable(check_mm_struct, la, page, 0); + page->pra_vaddr=la; + assert(page_ref(page) == 1); + //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); + } + else { //now current is existed, should fix it in the future + //swap_map_swappable(current->mm, la, page, 0); + //page->pra_vaddr=la; + //assert(page_ref(page) == 1); + //panic("pgdir_alloc_page: no pages. now current is existed, should fix it in the future\n"); + } + } + + } + + return page; +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} diff --git a/code/lab7/kern/mm/pmm.h b/code/lab7/kern/mm/pmm.h new file mode 100644 index 0000000..1e229a7 --- /dev/null +++ b/code/lab7/kern/mm/pmm.h @@ -0,0 +1,145 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); +struct Page *pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm); +void unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +void exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab7/kern/mm/swap.c b/code/lab7/kern/mm/swap.c new file mode 100644 index 0000000..281889d --- /dev/null +++ b/code/lab7/kern/mm/swap.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1 +#define CHECK_VALID_VIR_PAGE_NUM 5 +#define BEING_CHECK_VALID_VADDR 0X1000 +#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000 +// the max number of valid physical page for check +#define CHECK_VALID_PHY_PAGE_NUM 4 +// the max access seq number +#define MAX_SEQ_NO 10 + +static struct swap_manager *sm; +size_t max_swap_offset; + +volatile int swap_init_ok = 0; + +unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM]; + +unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO]; + +static void check_swap(void); + +int +swap_init(void) +{ + swapfs_init(); + + if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) + { + panic("bad max_swap_offset %08x.\n", max_swap_offset); + } + + + sm = &swap_manager_fifo; + int r = sm->init(); + + if (r == 0) + { + swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + check_swap(); + } + + return r; +} + +int +swap_init_mm(struct mm_struct *mm) +{ + return sm->init_mm(mm); +} + +int +swap_tick_event(struct mm_struct *mm) +{ + return sm->tick_event(mm); +} + +int +swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + return sm->map_swappable(mm, addr, page, swap_in); +} + +int +swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return sm->set_unswappable(mm, addr); +} + +volatile unsigned int swap_out_num=0; + +int +swap_out(struct mm_struct *mm, int n, int in_tick) +{ + int i; + for (i = 0; i != n; ++ i) + { + uintptr_t v; + //struct Page **ptr_page=NULL; + struct Page *page; + // cprintf("i %d, SWAP: call swap_out_victim\n",i); + int r = sm->swap_out_victim(mm, &page, in_tick); + if (r != 0) { + cprintf("i %d, swap_out: call swap_out_victim failed\n",i); + break; + } + //assert(!PageReserved(page)); + + //cprintf("SWAP: choose victim page 0x%08x\n", page); + + v=page->pra_vaddr; + pte_t *ptep = get_pte(mm->pgdir, v, 0); + assert((*ptep & PTE_P) != 0); + + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { + cprintf("SWAP: failed to save\n"); + sm->map_swappable(mm, v, page, 0); + continue; + } + else { + cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; + free_page(page); + } + + tlb_invalidate(mm->pgdir, v); + } + return i; +} + +int +swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) +{ + struct Page *result = alloc_page(); + assert(result!=NULL); + + pte_t *ptep = get_pte(mm->pgdir, addr, 0); + // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); + + int r; + if ((r = swapfs_read((*ptep), result)) != 0) + { + assert(r!=0); + } + cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr); + *ptr_result=result; + return 0; +} + + + +static inline void +check_content_set(void) +{ + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x1010 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x2010 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x3010 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + *(unsigned char *)0x4010 = 0x0d; + assert(pgfault_num==4); +} + +static inline int +check_content_access(void) +{ + int ret = sm->check_swap(); + return ret; +} + +struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM]; +pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM]; +unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM]; + +extern free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +check_swap(void) +{ + //backup mem env + int ret, count = 0, total = 0, i; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + //assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + + //now we set the phy pages env + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + extern struct mm_struct *check_mm_struct; + assert(check_mm_struct == NULL); + + check_mm_struct = mm; + + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + //setup the temp Page Table vaddr 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + pte_t *temp_ptep=NULL; + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); + assert(temp_ptep!= NULL); + cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + + for (i=0;iphy_page environment for page relpacement algorithm + + + pgfault_num=0; + + check_content_set(); + assert( nr_free == 0); + for(i = 0; ipgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + nr_free = nr_free_store; + free_list = free_list_store; + + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + cprintf("count is %d, total is %d\n",count,total); + //assert(count == 0); + + cprintf("check_swap() succeeded!\n"); +} diff --git a/code/lab7/kern/mm/swap.h b/code/lab7/kern/mm/swap.h new file mode 100644 index 0000000..5d4aea8 --- /dev/null +++ b/code/lab7/kern/mm/swap.h @@ -0,0 +1,65 @@ +#ifndef __KERN_MM_SWAP_H__ +#define __KERN_MM_SWAP_H__ + +#include +#include +#include +#include + +/* * + * swap_entry_t + * -------------------------------------------- + * | offset | reserved | 0 | + * -------------------------------------------- + * 24 bits 7 bits 1 bit + * */ + +#define MAX_SWAP_OFFSET_LIMIT (1 << 24) + +extern size_t max_swap_offset; + +/* * + * swap_offset - takes a swap_entry (saved in pte), and returns + * the corresponding offset in swap mem_map. + * */ +#define swap_offset(entry) ({ \ + size_t __offset = (entry >> 8); \ + if (!(__offset > 0 && __offset < max_swap_offset)) { \ + panic("invalid swap_entry_t = %08x.\n", entry); \ + } \ + __offset; \ + }) + +struct swap_manager +{ + const char *name; + /* Global initialization for the swap manager */ + int (*init) (void); + /* Initialize the priv data inside mm_struct */ + int (*init_mm) (struct mm_struct *mm); + /* Called when tick interrupt occured */ + int (*tick_event) (struct mm_struct *mm); + /* Called when map a swappable page into the mm_struct */ + int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); + /* When a page is marked as shared, this routine is called to + * delete the addr entry from the swap manager */ + int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); + /* Try to swap out a page, return then victim */ + int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); + /* check the page relpacement algorithm */ + int (*check_swap)(void); +}; + +extern volatile int swap_init_ok; +int swap_init(void); +int swap_init_mm(struct mm_struct *mm); +int swap_tick_event(struct mm_struct *mm); +int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); +int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr); +int swap_out(struct mm_struct *mm, int n, int in_tick); +int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result); + +//#define MEMBER_OFFSET(m,t) ((int)(&((t *)0)->m)) +//#define FROM_MEMBER(m,t,a) ((t *)((char *)(a) - MEMBER_OFFSET(m,t))) + +#endif diff --git a/code/lab7/kern/mm/swap_fifo.c b/code/lab7/kern/mm/swap_fifo.c new file mode 100644 index 0000000..4cb00c1 --- /dev/null +++ b/code/lab7/kern/mm/swap_fifo.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out + * page replacement algorithm is a low-overhead algorithm that requires little book-keeping on + * the part of the operating system. The idea is obvious from the name - the operating system + * keeps track of all the pages in memory in a queue, with the most recent arrival at the back, + * and the earliest arrival in front. When a page needs to be replaced, the page at the front + * of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs + * poorly in practical application. Thus, it is rarely used in its unmodified form. This + * algorithm experiences Belady's anomaly. + * + * Details of FIFO PRA + * (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can + * link these pages into pra_list_head according the time order. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list + * implementation. You should know howto USE: list_init, list_add(list_add_after), + * list_add_before, list_del, list_next, list_prev. Another tricky method is to transform + * a general list struct to a special struct (such as struct page). You can find some MACRO: + * le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc. + */ + +list_entry_t pra_list_head; +/* + * (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head. + * Now, From the memory control struct mm_struct, we can access FIFO PRA + */ +static int +_fifo_init_mm(struct mm_struct *mm) +{ + list_init(&pra_list_head); + mm->sm_priv = &pra_list_head; + //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + return 0; +} +/* + * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue + */ +static int +_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + list_entry_t *entry=&(page->pra_page_link); + + assert(entry != NULL && head != NULL); + //record the page access situlation + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + return 0; +} +/* + * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, + * then set the addr of addr of this page to ptr_page. + */ +static int +_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + assert(head != NULL); + assert(in_tick==0); + /* Select the victim */ + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1) unlink the earliest arrival page in front of pra_list_head qeueue + //(2) set the addr of addr of this page to ptr_page + return 0; +} + +static int +_fifo_check_swap(void) { + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==4); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==4); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==4); + cprintf("write Virt Page e in fifo_check_swap\n"); + *(unsigned char *)0x5000 = 0x0e; + assert(pgfault_num==5); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==5); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==6); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==7); + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==8); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==9); + return 0; +} + + +static int +_fifo_init(void) +{ + return 0; +} + +static int +_fifo_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return 0; +} + +static int +_fifo_tick_event(struct mm_struct *mm) +{ return 0; } + + +struct swap_manager swap_manager_fifo = +{ + .name = "fifo swap manager", + .init = &_fifo_init, + .init_mm = &_fifo_init_mm, + .tick_event = &_fifo_tick_event, + .map_swappable = &_fifo_map_swappable, + .set_unswappable = &_fifo_set_unswappable, + .swap_out_victim = &_fifo_swap_out_victim, + .check_swap = &_fifo_check_swap, +}; diff --git a/code/lab7/kern/mm/swap_fifo.h b/code/lab7/kern/mm/swap_fifo.h new file mode 100644 index 0000000..1d74269 --- /dev/null +++ b/code/lab7/kern/mm/swap_fifo.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_SWAP_FIFO_H__ +#define __KERN_MM_SWAP_FIFO_H__ + +#include +extern struct swap_manager swap_manager_fifo; + +#endif diff --git a/code/lab7/kern/mm/vmm.c b/code/lab7/kern/mm/vmm.c new file mode 100644 index 0000000..8ec698d --- /dev/null +++ b/code/lab7/kern/mm/vmm.c @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + vmm design include two parts: mm_struct (mm) & vma_struct (vma) + mm is the memory manager for the set of continuous virtual memory + area which have the same PDT. vma is a continuous virtual memory area. + There a linear link list for vma & a redblack link list for vma in mm. +--------------- + mm related functions: + golbal functions + struct mm_struct * mm_create(void) + void mm_destroy(struct mm_struct *mm) + int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) +-------------- + vma related functions: + global functions + struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) + void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) + struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) + local functions + inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) +--------------- + check correctness functions + void check_vmm(void); + void check_vma_struct(void); + void check_pgfault(void); +*/ + +static void check_vmm(void); +static void check_vma_struct(void); +static void check_pgfault(void); + +// mm_create - alloc a mm_struct & initialize it. +struct mm_struct * +mm_create(void) { + struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); + + if (mm != NULL) { + list_init(&(mm->mmap_list)); + mm->mmap_cache = NULL; + mm->pgdir = NULL; + mm->map_count = 0; + + if (swap_init_ok) swap_init_mm(mm); + else mm->sm_priv = NULL; + + set_mm_count(mm, 0); + sem_init(&(mm->mm_sem), 1); + } + return mm; +} + +// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +struct vma_struct * +vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); + + if (vma != NULL) { + vma->vm_start = vm_start; + vma->vm_end = vm_end; + vma->vm_flags = vm_flags; + } + return vma; +} + + +// find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +struct vma_struct * +find_vma(struct mm_struct *mm, uintptr_t addr) { + struct vma_struct *vma = NULL; + if (mm != NULL) { + vma = mm->mmap_cache; + if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { + bool found = 0; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + vma = le2vma(le, list_link); + if (addr < vma->vm_end) { + found = 1; + break; + } + } + if (!found) { + vma = NULL; + } + } + if (vma != NULL) { + mm->mmap_cache = vma; + } + } + return vma; +} + + +// check_vma_overlap - check if vma1 overlaps vma2 ? +static inline void +check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { + assert(prev->vm_start < prev->vm_end); + assert(prev->vm_end <= next->vm_start); + assert(next->vm_start < next->vm_end); +} + + +// insert_vma_struct -insert vma in mm's list link +void +insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + assert(vma->vm_start < vma->vm_end); + list_entry_t *list = &(mm->mmap_list); + list_entry_t *le_prev = list, *le_next; + + list_entry_t *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *mmap_prev = le2vma(le, list_link); + if (mmap_prev->vm_start > vma->vm_start) { + break; + } + le_prev = le; + } + + le_next = list_next(le_prev); + + /* check overlap */ + if (le_prev != list) { + check_vma_overlap(le2vma(le_prev, list_link), vma); + } + if (le_next != list) { + check_vma_overlap(vma, le2vma(le_next, list_link)); + } + + vma->vm_mm = mm; + list_add_after(le_prev, &(vma->list_link)); + + mm->map_count ++; +} + +// mm_destroy - free mm and mm internal fields +void +mm_destroy(struct mm_struct *mm) { + assert(mm_count(mm) == 0); + + list_entry_t *list = &(mm->mmap_list), *le; + while ((le = list_next(list)) != list) { + list_del(le); + kfree(le2vma(le, list_link)); //kfree vma + } + kfree(mm); //kfree mm + mm=NULL; +} + +int +mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store) { + uintptr_t start = ROUNDDOWN(addr, PGSIZE), end = ROUNDUP(addr + len, PGSIZE); + if (!USER_ACCESS(start, end)) { + return -E_INVAL; + } + + assert(mm != NULL); + + int ret = -E_INVAL; + + struct vma_struct *vma; + if ((vma = find_vma(mm, start)) != NULL && end > vma->vm_start) { + goto out; + } + ret = -E_NO_MEM; + + if ((vma = vma_create(start, end, vm_flags)) == NULL) { + goto out; + } + insert_vma_struct(mm, vma); + if (vma_store != NULL) { + *vma_store = vma; + } + ret = 0; + +out: + return ret; +} + +int +dup_mmap(struct mm_struct *to, struct mm_struct *from) { + assert(to != NULL && from != NULL); + list_entry_t *list = &(from->mmap_list), *le = list; + while ((le = list_prev(le)) != list) { + struct vma_struct *vma, *nvma; + vma = le2vma(le, list_link); + nvma = vma_create(vma->vm_start, vma->vm_end, vma->vm_flags); + if (nvma == NULL) { + return -E_NO_MEM; + } + + insert_vma_struct(to, nvma); + + bool share = 0; + if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share) != 0) { + return -E_NO_MEM; + } + } + return 0; +} + +void +exit_mmap(struct mm_struct *mm) { + assert(mm != NULL && mm_count(mm) == 0); + pde_t *pgdir = mm->pgdir; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + unmap_range(pgdir, vma->vm_start, vma->vm_end); + } + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + exit_range(pgdir, vma->vm_start, vma->vm_end); + } +} + +bool +copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable) { + if (!user_mem_check(mm, (uintptr_t)src, len, writable)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +bool +copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len) { + if (!user_mem_check(mm, (uintptr_t)dst, len, 1)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +// vmm_init - initialize virtual memory management +// - now just call check_vmm to check correctness of vmm +void +vmm_init(void) { + check_vmm(); +} + +// check_vmm - check correctness of vmm +static void +check_vmm(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_vma_struct(); + check_pgfault(); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vmm() succeeded.\n"); +} + +static void +check_vma_struct(void) { + size_t nr_free_pages_store = nr_free_pages(); + + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + int step1 = 10, step2 = step1 * 10; + + int i; + for (i = step1; i >= 0; i --) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + for (i = step1 + 1; i <= step2; i ++) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + list_entry_t *le = list_next(&(mm->mmap_list)); + + for (i = 0; i <= step2; i ++) { + assert(le != &(mm->mmap_list)); + struct vma_struct *mmap = le2vma(le, list_link); + assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); + le = list_next(le); + } + + for (i = 0; i < 5 * step2 + 2; i ++) { + struct vma_struct *vma = find_vma(mm, i); + assert(vma != NULL); + int j = i / 5; + if (i >= 5 * j + 2) { + j ++; + } + assert(vma->vm_start == j * 5 && vma->vm_end == j * 5 + 2); + } + + mm_destroy(mm); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vma_struct() succeeded!\n"); +} + +struct mm_struct *check_mm_struct; + +// check_pgfault - check correctness of pgfault handler +static void +check_pgfault(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_mm_struct = mm_create(); + assert(check_mm_struct != NULL); + + struct mm_struct *mm = check_mm_struct; + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + uintptr_t addr = 0x100; + assert(find_vma(mm, addr) == vma); + + int i, sum = 0; + for (i = 0; i < 100; i ++) { + *(char *)(addr + i) = i; + sum += i; + } + for (i = 0; i < 100; i ++) { + sum -= *(char *)(addr + i); + } + assert(sum == 0); + + page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + free_page(pa2page(pgdir[0])); + pgdir[0] = 0; + + mm->pgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_pgfault() succeeded!\n"); +} +//page fault number +volatile unsigned int pgfault_num=0; + +/* do_pgfault - interrupt handler to process the page fault execption + * @mm : the control struct for a set of vma using the same PDT + * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware + * @addr : the addr which causes a memory access exception, (the contents of the CR2 register) + * + * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault + * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing + * the exception and recovering from it. + * (1) The contents of the CR2 register. The processor loads the CR2 register with the + * 32-bit linear address that generated the exception. The do_pgfault fun can + * use this address to locate the corresponding page directory and page-table + * entries. + * (2) An error code on the kernel stack. The error code for a page fault has a format different from + * that for other exceptions. The error code tells the exception handler three things: + * -- The P flag (bit 0) indicates whether the exception was due to a not-present page (0) + * or to either an access rights violation or the use of a reserved bit (1). + * -- The W/R flag (bit 1) indicates whether the memory access that caused the exception + * was a read (0) or write (1). + * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) + * or supervisor mode (0) at the time of the exception. + */ +int +do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { + int ret = -E_INVAL; + //try to find a vma which include addr + struct vma_struct *vma = find_vma(mm, addr); + + pgfault_num++; + //If the addr is in the range of a mm's vma? + if (vma == NULL || vma->vm_start > addr) { + cprintf("not valid addr %x, and can not find it in vma\n", addr); + goto failed; + } + //check the error_code + switch (error_code & 3) { + default: + /* error code flag : default is 3 ( W/R=1, P=1): write, present */ + case 2: /* error code flag : (W/R=1, P=0): write, not present */ + if (!(vma->vm_flags & VM_WRITE)) { + cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); + goto failed; + } + break; + case 1: /* error code flag : (W/R=0, P=1): read, present */ + cprintf("do_pgfault failed: error code flag = read AND present\n"); + goto failed; + case 0: /* error code flag : (W/R=0, P=0): read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { + cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); + goto failed; + } + } + /* IF (write an existed addr ) OR + * (write an non_existed addr && addr is writable) OR + * (read an non_existed addr && addr is readable) + * THEN + * continue process + */ + uint32_t perm = PTE_U; + if (vma->vm_flags & VM_WRITE) { + perm |= PTE_W; + } + addr = ROUNDDOWN(addr, PGSIZE); + + ret = -E_NO_MEM; + + pte_t *ptep=NULL; + /*LAB3 EXERCISE 1: YOUR CODE + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * get_pte : get an pte and return the kernel virtual address of this pte for la + * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') + * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup + * an addr map pa<--->la with linear address la and the PDT pgdir + * DEFINES: + * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + * VARIABLES: + * mm->pgdir : the PDT of these vma + * + */ +#if 0 + /*LAB3 EXERCISE 1: YOUR CODE*/ + ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + if (*ptep == 0) { + //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + + } + else { + /*LAB3 EXERCISE 2: YOUR CODE + * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, + * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, + * find the addr of disk page, read the content of disk page into this memroy page + * page_insert : build the map of phy addr of an Page with the linear addr la + * swap_map_swappable : set the page swappable + */ + if(swap_init_ok) { + struct Page *page=NULL; + //(1)According to the mm AND addr, try to load the content of right disk page + // into the memory which page managed. + //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr + //(3) make the page swappable. + //(4) [NOTICE]: you myabe need to update your lab3's implementation for LAB5's normal execution. + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed; + } + } +#endif + ret = 0; +failed: + return ret; +} + +bool +user_mem_check(struct mm_struct *mm, uintptr_t addr, size_t len, bool write) { + if (mm != NULL) { + if (!USER_ACCESS(addr, addr + len)) { + return 0; + } + struct vma_struct *vma; + uintptr_t start = addr, end = addr + len; + while (start < end) { + if ((vma = find_vma(mm, start)) == NULL || start < vma->vm_start) { + return 0; + } + if (!(vma->vm_flags & ((write) ? VM_WRITE : VM_READ))) { + return 0; + } + if (write && (vma->vm_flags & VM_STACK)) { + if (start < vma->vm_start + PGSIZE) { //check stack start & size + return 0; + } + } + start = vma->vm_end; + } + return 1; + } + return KERN_ACCESS(addr, addr + len); +} + diff --git a/code/lab7/kern/mm/vmm.h b/code/lab7/kern/mm/vmm.h new file mode 100644 index 0000000..c467dba --- /dev/null +++ b/code/lab7/kern/mm/vmm.h @@ -0,0 +1,108 @@ +#ifndef __KERN_MM_VMM_H__ +#define __KERN_MM_VMM_H__ + +#include +#include +#include +#include +#include +#include + +//pre define +struct mm_struct; + +// the virtual continuous memory area(vma) +struct vma_struct { + struct mm_struct *vm_mm; // the set of vma using the same PDT + uintptr_t vm_start; // start addr of vma + uintptr_t vm_end; // end addr of vma + uint32_t vm_flags; // flags of vma + list_entry_t list_link; // linear list link which sorted by start addr of vma +}; + +#define le2vma(le, member) \ + to_struct((le), struct vma_struct, member) + +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 +#define VM_STACK 0x00000008 + +// the control struct for a set of vma using the same PDT +struct mm_struct { + list_entry_t mmap_list; // linear list link which sorted by start addr of vma + struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose + pde_t *pgdir; // the PDT of these vma + int map_count; // the count of these vma + void *sm_priv; // the private data for swap manager + atomic_t mm_count; // the number ofprocess which shared the mm + semaphore_t mm_sem; // mutex for using dup_mmap fun to duplicat the mm + int locked_by; // the lock owner process's pid + +}; + +struct vma_struct *find_vma(struct mm_struct *mm, uintptr_t addr); +struct vma_struct *vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags); +void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma); + +struct mm_struct *mm_create(void); +void mm_destroy(struct mm_struct *mm); + +void vmm_init(void); +int mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store); +int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr); + +int mm_unmap(struct mm_struct *mm, uintptr_t addr, size_t len); +int dup_mmap(struct mm_struct *to, struct mm_struct *from); +void exit_mmap(struct mm_struct *mm); +uintptr_t get_unmapped_area(struct mm_struct *mm, size_t len); +int mm_brk(struct mm_struct *mm, uintptr_t addr, size_t len); + +extern volatile unsigned int pgfault_num; +extern struct mm_struct *check_mm_struct; + +bool user_mem_check(struct mm_struct *mm, uintptr_t start, size_t len, bool write); +bool copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable); +bool copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len); + +static inline int +mm_count(struct mm_struct *mm) { + return atomic_read(&(mm->mm_count)); +} + +static inline void +set_mm_count(struct mm_struct *mm, int val) { + atomic_set(&(mm->mm_count), val); +} + +static inline int +mm_count_inc(struct mm_struct *mm) { + return atomic_add_return(&(mm->mm_count), 1); +} + +static inline int +mm_count_dec(struct mm_struct *mm) { + return atomic_sub_return(&(mm->mm_count), 1); +} + +static inline void +lock_mm(struct mm_struct *mm) { + if (mm != NULL) { + down(&(mm->mm_sem)); + if (current != NULL) { + mm->locked_by = current->pid; + } + } +} + +static inline void +unlock_mm(struct mm_struct *mm) { + if (mm != NULL) { + up(&(mm->mm_sem)); + mm->locked_by = 0; + } +} + +#endif /* !__KERN_MM_VMM_H__ */ + diff --git a/code/lab7/kern/process/entry.S b/code/lab7/kern/process/entry.S new file mode 100644 index 0000000..7482e23 --- /dev/null +++ b/code/lab7/kern/process/entry.S @@ -0,0 +1,10 @@ +.text +.globl kernel_thread_entry +kernel_thread_entry: # void kernel_thread(void) + + pushl %edx # push arg + call *%ebx # call fn + + pushl %eax # save the return value of fn(arg) + call do_exit # call do_exit to terminate current thread + diff --git a/code/lab7/kern/process/proc.c b/code/lab7/kern/process/proc.c new file mode 100644 index 0000000..5ca778f --- /dev/null +++ b/code/lab7/kern/process/proc.c @@ -0,0 +1,872 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------- process/thread mechanism design&implementation ------------- +(an simplified Linux process/thread mechanism ) +introduction: + ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads +for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to +manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory). +------------------------------ +process state : meaning -- reason + PROC_UNINIT : uninitialized -- alloc_proc + PROC_SLEEPING : sleeping -- try_free_pages, do_wait, do_sleep + PROC_RUNNABLE : runnable(maybe running) -- proc_init, wakeup_proc, + PROC_ZOMBIE : almost dead -- do_exit + +----------------------------- +process state changing: + + alloc_proc RUNNING + + +--<----<--+ + + + proc_run + + V +-->---->--+ +PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING -- + A + + + | +--- do_exit --> PROC_ZOMBIE + + + + + -----------------------wakeup_proc---------------------------------- +----------------------------- +process relations +parent: proc->parent (proc is children) +children: proc->cptr (proc is parent) +older sibling: proc->optr (proc is younger sibling) +younger sibling: proc->yptr (proc is older sibling) +----------------------------- +related syscall for process: +SYS_exit : process exit, -->do_exit +SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc +SYS_wait : wait process -->do_wait +SYS_exec : after fork, process execute a program -->load a program and refresh the mm +SYS_clone : create child thread -->do_fork-->wakeup_proc +SYS_yield : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process +SYS_sleep : process sleep -->do_sleep +SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING + -->wakeup_proc-->do_wait-->do_exit +SYS_getpid : get the process's pid + +*/ + +// the process set's list +list_entry_t proc_list; + +#define HASH_SHIFT 10 +#define HASH_LIST_SIZE (1 << HASH_SHIFT) +#define pid_hashfn(x) (hash32(x, HASH_SHIFT)) + +// has list for process set based on pid +static list_entry_t hash_list[HASH_LIST_SIZE]; + +// idle proc +struct proc_struct *idleproc = NULL; +// init proc +struct proc_struct *initproc = NULL; +// current proc +struct proc_struct *current = NULL; + +static int nr_process = 0; + +void kernel_thread_entry(void); +void forkrets(struct trapframe *tf); +void switch_to(struct context *from, struct context *to); + +// alloc_proc - alloc a proc_struct and init all fields of proc_struct +static struct proc_struct * +alloc_proc(void) { + struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); + if (proc != NULL) { + //LAB4:EXERCISE1 YOUR CODE + /* + * below fields in proc_struct need to be initialized + * enum proc_state state; // Process state + * int pid; // Process ID + * int runs; // the running times of Proces + * uintptr_t kstack; // Process kernel stack + * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + * struct proc_struct *parent; // the parent process + * struct mm_struct *mm; // Process's memory management field + * struct context context; // Switch here to run process + * struct trapframe *tf; // Trap frame for current interrupt + * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + * uint32_t flags; // Process flag + * char name[PROC_NAME_LEN + 1]; // Process name + */ + } + return proc; +} + +// set_proc_name - set the name of proc +char * +set_proc_name(struct proc_struct *proc, const char *name) { + memset(proc->name, 0, sizeof(proc->name)); + return memcpy(proc->name, name, PROC_NAME_LEN); +} + +// get_proc_name - get the name of proc +char * +get_proc_name(struct proc_struct *proc) { + static char name[PROC_NAME_LEN + 1]; + memset(name, 0, sizeof(name)); + return memcpy(name, proc->name, PROC_NAME_LEN); +} + +// set_links - set the relation links of process +static void +set_links(struct proc_struct *proc) { + list_add(&proc_list, &(proc->list_link)); + proc->yptr = NULL; + if ((proc->optr = proc->parent->cptr) != NULL) { + proc->optr->yptr = proc; + } + proc->parent->cptr = proc; + nr_process ++; +} + +// remove_links - clean the relation links of process +static void +remove_links(struct proc_struct *proc) { + list_del(&(proc->list_link)); + if (proc->optr != NULL) { + proc->optr->yptr = proc->yptr; + } + if (proc->yptr != NULL) { + proc->yptr->optr = proc->optr; + } + else { + proc->parent->cptr = proc->optr; + } + nr_process --; +} + +// get_pid - alloc a unique pid for process +static int +get_pid(void) { + static_assert(MAX_PID > MAX_PROCESS); + struct proc_struct *proc; + list_entry_t *list = &proc_list, *le; + static int next_safe = MAX_PID, last_pid = MAX_PID; + if (++ last_pid >= MAX_PID) { + last_pid = 1; + goto inside; + } + if (last_pid >= next_safe) { + inside: + next_safe = MAX_PID; + repeat: + le = list; + while ((le = list_next(le)) != list) { + proc = le2proc(le, list_link); + if (proc->pid == last_pid) { + if (++ last_pid >= next_safe) { + if (last_pid >= MAX_PID) { + last_pid = 1; + } + next_safe = MAX_PID; + goto repeat; + } + } + else if (proc->pid > last_pid && next_safe > proc->pid) { + next_safe = proc->pid; + } + } + } + return last_pid; +} + +// proc_run - make process "proc" running on cpu +// NOTE: before call switch_to, should load base addr of "proc"'s new PDT +void +proc_run(struct proc_struct *proc) { + if (proc != current) { + bool intr_flag; + struct proc_struct *prev = current, *next = proc; + local_intr_save(intr_flag); + { + current = proc; + load_esp0(next->kstack + KSTACKSIZE); + lcr3(next->cr3); + switch_to(&(prev->context), &(next->context)); + } + local_intr_restore(intr_flag); + } +} + +// forkret -- the first kernel entry point of a new thread/process +// NOTE: the addr of forkret is setted in copy_thread function +// after switch_to, the current proc will execute here. +static void +forkret(void) { + forkrets(current->tf); +} + +// hash_proc - add proc into proc hash_list +static void +hash_proc(struct proc_struct *proc) { + list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); +} + +// unhash_proc - delete proc from proc hash_list +static void +unhash_proc(struct proc_struct *proc) { + list_del(&(proc->hash_link)); +} + +// find_proc - find proc frome proc hash_list according to pid +struct proc_struct * +find_proc(int pid) { + if (0 < pid && pid < MAX_PID) { + list_entry_t *list = hash_list + pid_hashfn(pid), *le = list; + while ((le = list_next(le)) != list) { + struct proc_struct *proc = le2proc(le, hash_link); + if (proc->pid == pid) { + return proc; + } + } + } + return NULL; +} + +// kernel_thread - create a kernel thread using "fn" function +// NOTE: the contents of temp trapframe tf will be copied to +// proc->tf in do_fork-->copy_thread function +int +kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { + struct trapframe tf; + memset(&tf, 0, sizeof(struct trapframe)); + tf.tf_cs = KERNEL_CS; + tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; + tf.tf_regs.reg_ebx = (uint32_t)fn; + tf.tf_regs.reg_edx = (uint32_t)arg; + tf.tf_eip = (uint32_t)kernel_thread_entry; + return do_fork(clone_flags | CLONE_VM, 0, &tf); +} + +// setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack +static int +setup_kstack(struct proc_struct *proc) { + struct Page *page = alloc_pages(KSTACKPAGE); + if (page != NULL) { + proc->kstack = (uintptr_t)page2kva(page); + return 0; + } + return -E_NO_MEM; +} + +// put_kstack - free the memory space of process kernel stack +static void +put_kstack(struct proc_struct *proc) { + free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE); +} + +// setup_pgdir - alloc one page as PDT +static int +setup_pgdir(struct mm_struct *mm) { + struct Page *page; + if ((page = alloc_page()) == NULL) { + return -E_NO_MEM; + } + pde_t *pgdir = page2kva(page); + memcpy(pgdir, boot_pgdir, PGSIZE); + pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_P | PTE_W; + mm->pgdir = pgdir; + return 0; +} + +// put_pgdir - free the memory space of PDT +static void +put_pgdir(struct mm_struct *mm) { + free_page(kva2page(mm->pgdir)); +} + +// copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags +// - if clone_flags & CLONE_VM, then "share" ; else "duplicate" +static int +copy_mm(uint32_t clone_flags, struct proc_struct *proc) { + struct mm_struct *mm, *oldmm = current->mm; + + /* current is a kernel thread */ + if (oldmm == NULL) { + return 0; + } + if (clone_flags & CLONE_VM) { + mm = oldmm; + goto good_mm; + } + + int ret = -E_NO_MEM; + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + + lock_mm(oldmm); + { + ret = dup_mmap(mm, oldmm); + } + unlock_mm(oldmm); + + if (ret != 0) { + goto bad_dup_cleanup_mmap; + } + +good_mm: + mm_count_inc(mm); + proc->mm = mm; + proc->cr3 = PADDR(mm->pgdir); + return 0; +bad_dup_cleanup_mmap: + exit_mmap(mm); + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + return ret; +} + +// copy_thread - setup the trapframe on the process's kernel stack top and +// - setup the kernel entry point and stack of process +static void +copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { + proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; + *(proc->tf) = *tf; + proc->tf->tf_regs.reg_eax = 0; + proc->tf->tf_esp = esp; + proc->tf->tf_eflags |= FL_IF; + + proc->context.eip = (uintptr_t)forkret; + proc->context.esp = (uintptr_t)(proc->tf); +} + +/* do_fork - parent process for a new child process + * @clone_flags: used to guide how to clone the child process + * @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread. + * @tf: the trapframe info, which will be copied to child process's proc->tf + */ +int +do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { + int ret = -E_NO_FREE_PROC; + struct proc_struct *proc; + if (nr_process >= MAX_PROCESS) { + goto fork_out; + } + ret = -E_NO_MEM; + //LAB4:EXERCISE2 YOUR CODE + /* + * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * alloc_proc: create a proc struct and init fields (lab4:exercise1) + * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack + * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags + * if clone_flags & CLONE_VM, then "share" ; else "duplicate" + * copy_thread: setup the trapframe on the process's kernel stack top and + * setup the kernel entry point and stack of process + * hash_proc: add proc into proc hash_list + * get_pid: alloc a unique pid for process + * wakup_proc: set proc->state = PROC_RUNNABLE + * VARIABLES: + * proc_list: the process set's list + * nr_process: the number of process set + */ + + // 1. call alloc_proc to allocate a proc_struct + // 2. call setup_kstack to allocate a kernel stack for child process + // 3. call copy_mm to dup OR share mm according clone_flag + // 4. call copy_thread to setup tf & context in proc_struct + // 5. insert proc_struct into hash_list && proc_list + // 6. call wakup_proc to make the new child process RUNNABLE + // 7. set ret vaule using child proc's pid +fork_out: + return ret; + +bad_fork_cleanup_kstack: + put_kstack(proc); +bad_fork_cleanup_proc: + kfree(proc); + goto fork_out; +} + +// do_exit - called by sys_exit +// 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process +// 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself. +// 3. call scheduler to switch to other process +int +do_exit(int error_code) { + if (current == idleproc) { + panic("idleproc exit.\n"); + } + if (current == initproc) { + panic("initproc exit.\n"); + } + + struct mm_struct *mm = current->mm; + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + current->state = PROC_ZOMBIE; + current->exit_code = error_code; + + bool intr_flag; + struct proc_struct *proc; + local_intr_save(intr_flag); + { + proc = current->parent; + if (proc->wait_state == WT_CHILD) { + wakeup_proc(proc); + } + while (current->cptr != NULL) { + proc = current->cptr; + current->cptr = proc->optr; + + proc->yptr = NULL; + if ((proc->optr = initproc->cptr) != NULL) { + initproc->cptr->yptr = proc; + } + proc->parent = initproc; + initproc->cptr = proc; + if (proc->state == PROC_ZOMBIE) { + if (initproc->wait_state == WT_CHILD) { + wakeup_proc(initproc); + } + } + } + } + local_intr_restore(intr_flag); + + schedule(); + panic("do_exit will not return!! %d.\n", current->pid); +} + +/* load_icode - load the content of binary program(ELF format) as the new content of current process + * @binary: the memory addr of the content of binary program + * @size: the size of the content of binary program + */ +static int +load_icode(unsigned char *binary, size_t size) { + if (current->mm != NULL) { + panic("load_icode: current->mm must be empty.\n"); + } + + int ret = -E_NO_MEM; + struct mm_struct *mm; + //(1) create a new mm for current process + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + //(2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + //(3) copy TEXT/DATA section, build BSS parts in binary to memory space of process + struct Page *page; + //(3.1) get the file header of the bianry program (ELF format) + struct elfhdr *elf = (struct elfhdr *)binary; + //(3.2) get the entry of the program section headers of the bianry program (ELF format) + struct proghdr *ph = (struct proghdr *)(binary + elf->e_phoff); + //(3.3) This program is valid? + if (elf->e_magic != ELF_MAGIC) { + ret = -E_INVAL_ELF; + goto bad_elf_cleanup_pgdir; + } + + uint32_t vm_flags, perm; + struct proghdr *ph_end = ph + elf->e_phnum; + for (; ph < ph_end; ph ++) { + //(3.4) find every program section headers + if (ph->p_type != ELF_PT_LOAD) { + continue ; + } + if (ph->p_filesz > ph->p_memsz) { + ret = -E_INVAL_ELF; + goto bad_cleanup_mmap; + } + if (ph->p_filesz == 0) { + continue ; + } + //(3.5) call mm_map fun to setup the new vma ( ph->p_va, ph->p_memsz) + vm_flags = 0, perm = PTE_U; + if (ph->p_flags & ELF_PF_X) vm_flags |= VM_EXEC; + if (ph->p_flags & ELF_PF_W) vm_flags |= VM_WRITE; + if (ph->p_flags & ELF_PF_R) vm_flags |= VM_READ; + if (vm_flags & VM_WRITE) perm |= PTE_W; + if ((ret = mm_map(mm, ph->p_va, ph->p_memsz, vm_flags, NULL)) != 0) { + goto bad_cleanup_mmap; + } + unsigned char *from = binary + ph->p_offset; + size_t off, size; + uintptr_t start = ph->p_va, end, la = ROUNDDOWN(start, PGSIZE); + + ret = -E_NO_MEM; + + //(3.6) alloc memory, and copy the contents of every program section (from, from+end) to process's memory (la, la+end) + end = ph->p_va + ph->p_filesz; + //(3.6.1) copy TEXT/DATA section of bianry program + while (start < end) { + if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { + goto bad_cleanup_mmap; + } + off = start - la, size = PGSIZE - off, la += PGSIZE; + if (end < la) { + size -= la - end; + } + memcpy(page2kva(page) + off, from, size); + start += size, from += size; + } + + //(3.6.2) build BSS section of binary program + end = ph->p_va + ph->p_memsz; + if (start < la) { + /* ph->p_memsz == ph->p_filesz */ + if (start == end) { + continue ; + } + off = start + PGSIZE - la, size = PGSIZE - off; + if (end < la) { + size -= la - end; + } + memset(page2kva(page) + off, 0, size); + start += size; + assert((end < la && start == end) || (end >= la && start == la)); + } + while (start < end) { + if ((page = pgdir_alloc_page(mm->pgdir, la, perm)) == NULL) { + goto bad_cleanup_mmap; + } + off = start - la, size = PGSIZE - off, la += PGSIZE; + if (end < la) { + size -= la - end; + } + memset(page2kva(page) + off, 0, size); + start += size; + } + } + //(4) build user stack memory + vm_flags = VM_READ | VM_WRITE | VM_STACK; + if ((ret = mm_map(mm, USTACKTOP - USTACKSIZE, USTACKSIZE, vm_flags, NULL)) != 0) { + goto bad_cleanup_mmap; + } + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-2*PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-3*PGSIZE , PTE_USER) != NULL); + assert(pgdir_alloc_page(mm->pgdir, USTACKTOP-4*PGSIZE , PTE_USER) != NULL); + + //(5) set current process's mm, sr3, and set CR3 reg = physical addr of Page Directory + mm_count_inc(mm); + current->mm = mm; + current->cr3 = PADDR(mm->pgdir); + lcr3(PADDR(mm->pgdir)); + + //(6) setup trapframe for user environment + struct trapframe *tf = current->tf; + memset(tf, 0, sizeof(struct trapframe)); + /* LAB5:EXERCISE1 YOUR CODE + * should set tf_cs,tf_ds,tf_es,tf_ss,tf_esp,tf_eip,tf_eflags + * NOTICE: If we set trapframe correctly, then the user level process can return to USER MODE from kernel. So + * tf_cs should be USER_CS segment (see memlayout.h) + * tf_ds=tf_es=tf_ss should be USER_DS segment + * tf_esp should be the top addr of user stack (USTACKTOP) + * tf_eip should be the entry point of this binary program (elf->e_entry) + * tf_eflags should be set to enable computer to produce Interrupt + */ + ret = 0; +out: + return ret; +bad_cleanup_mmap: + exit_mmap(mm); +bad_elf_cleanup_pgdir: + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + goto out; +} + +// do_execve - call exit_mmap(mm)&pug_pgdir(mm) to reclaim memory space of current process +// - call load_icode to setup new memory space accroding binary prog. +int +do_execve(const char *name, size_t len, unsigned char *binary, size_t size) { + struct mm_struct *mm = current->mm; + if (!user_mem_check(mm, (uintptr_t)name, len, 0)) { + return -E_INVAL; + } + if (len > PROC_NAME_LEN) { + len = PROC_NAME_LEN; + } + + char local_name[PROC_NAME_LEN + 1]; + memset(local_name, 0, sizeof(local_name)); + memcpy(local_name, name, len); + + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + int ret; + if ((ret = load_icode(binary, size)) != 0) { + goto execve_exit; + } + set_proc_name(current, local_name); + return 0; + +execve_exit: + do_exit(ret); + panic("already exit: %e.\n", ret); +} + +// do_yield - ask the scheduler to reschedule +int +do_yield(void) { + current->need_resched = 1; + return 0; +} + +// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack +// - proc struct of this child. +// NOTE: only after do_wait function, all resources of the child proces are free. +int +do_wait(int pid, int *code_store) { + struct mm_struct *mm = current->mm; + if (code_store != NULL) { + if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) { + return -E_INVAL; + } + } + + struct proc_struct *proc; + bool intr_flag, haskid; +repeat: + haskid = 0; + if (pid != 0) { + proc = find_proc(pid); + if (proc != NULL && proc->parent == current) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + else { + proc = current->cptr; + for (; proc != NULL; proc = proc->optr) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + if (haskid) { + current->state = PROC_SLEEPING; + current->wait_state = WT_CHILD; + schedule(); + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + goto repeat; + } + return -E_BAD_PROC; + +found: + if (proc == idleproc || proc == initproc) { + panic("wait idleproc or initproc.\n"); + } + if (code_store != NULL) { + *code_store = proc->exit_code; + } + local_intr_save(intr_flag); + { + unhash_proc(proc); + remove_links(proc); + } + local_intr_restore(intr_flag); + put_kstack(proc); + kfree(proc); + return 0; +} + +// do_kill - kill process with pid by set this process's flags with PF_EXITING +int +do_kill(int pid) { + struct proc_struct *proc; + if ((proc = find_proc(pid)) != NULL) { + if (!(proc->flags & PF_EXITING)) { + proc->flags |= PF_EXITING; + if (proc->wait_state & WT_INTERRUPTED) { + wakeup_proc(proc); + } + return 0; + } + return -E_KILLED; + } + return -E_INVAL; +} + +// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread +static int +kernel_execve(const char *name, unsigned char *binary, size_t size) { + int ret, len = strlen(name); + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (len), "b" (binary), "D" (size) + : "memory"); + return ret; +} + +#define __KERNEL_EXECVE(name, binary, size) ({ \ + cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \ + current->pid, name); \ + kernel_execve(name, binary, (size_t)(size)); \ + }) + +#define KERNEL_EXECVE(x) ({ \ + extern unsigned char _binary_obj___user_##x##_out_start[], \ + _binary_obj___user_##x##_out_size[]; \ + __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start, \ + _binary_obj___user_##x##_out_size); \ + }) + +#define __KERNEL_EXECVE2(x, xstart, xsize) ({ \ + extern unsigned char xstart[], xsize[]; \ + __KERNEL_EXECVE(#x, xstart, (size_t)xsize); \ + }) + +#define KERNEL_EXECVE2(x, xstart, xsize) __KERNEL_EXECVE2(x, xstart, xsize) + +// user_main - kernel thread used to exec a user program +static int +user_main(void *arg) { +#ifdef TEST + KERNEL_EXECVE2(TEST, TESTSTART, TESTSIZE); +#else + KERNEL_EXECVE(exit); +#endif + panic("user_main execve failed.\n"); +} + +// init_main - the second kernel thread used to create user_main kernel threads +static int +init_main(void *arg) { + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = kallocated(); + + int pid = kernel_thread(user_main, NULL, 0); + if (pid <= 0) { + panic("create user_main failed.\n"); + } + extern void check_sync(void); + check_sync(); // check philosopher sync problem + + while (do_wait(0, NULL) == 0) { + schedule(); + } + + cprintf("all user-mode processes have quit.\n"); + assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL); + assert(nr_process == 2); + assert(list_next(&proc_list) == &(initproc->list_link)); + assert(list_prev(&proc_list) == &(initproc->list_link)); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == kallocated()); + cprintf("init check memory pass.\n"); + return 0; +} + +// proc_init - set up the first kernel thread idleproc "idle" by itself and +// - create the second kernel thread init_main +void +proc_init(void) { + int i; + + list_init(&proc_list); + for (i = 0; i < HASH_LIST_SIZE; i ++) { + list_init(hash_list + i); + } + + if ((idleproc = alloc_proc()) == NULL) { + panic("cannot alloc idleproc.\n"); + } + + idleproc->pid = 0; + idleproc->state = PROC_RUNNABLE; + idleproc->kstack = (uintptr_t)bootstack; + idleproc->need_resched = 1; + set_proc_name(idleproc, "idle"); + nr_process ++; + + current = idleproc; + + int pid = kernel_thread(init_main, NULL, 0); + if (pid <= 0) { + panic("create init_main failed.\n"); + } + + initproc = find_proc(pid); + set_proc_name(initproc, "init"); + + assert(idleproc != NULL && idleproc->pid == 0); + assert(initproc != NULL && initproc->pid == 1); +} + +// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works +void +cpu_idle(void) { + while (1) { + if (current->need_resched) { + schedule(); + } + } +} + +//FOR LAB6, set the process's priority (bigger value will get more CPU time) +void +lab6_set_priority(uint32_t priority) +{ + if (priority == 0) + current->lab6_priority = 1; + else current->lab6_priority = priority; +} + +// do_sleep - set current process state to sleep and add timer with "time" +// - then call scheduler. if process run again, delete timer first. +int +do_sleep(unsigned int time) { + if (time == 0) { + return 0; + } + bool intr_flag; + local_intr_save(intr_flag); + timer_t __timer, *timer = timer_init(&__timer, current, time); + current->state = PROC_SLEEPING; + current->wait_state = WT_TIMER; + add_timer(timer); + local_intr_restore(intr_flag); + + schedule(); + + del_timer(timer); + return 0; +} diff --git a/code/lab7/kern/process/proc.h b/code/lab7/kern/process/proc.h new file mode 100644 index 0000000..5c159e7 --- /dev/null +++ b/code/lab7/kern/process/proc.h @@ -0,0 +1,100 @@ +#ifndef __KERN_PROCESS_PROC_H__ +#define __KERN_PROCESS_PROC_H__ + +#include +#include +#include +#include +#include + + +// process's state in his life cycle +enum proc_state { + PROC_UNINIT = 0, // uninitialized + PROC_SLEEPING, // sleeping + PROC_RUNNABLE, // runnable(maybe running) + PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource +}; + +// Saved registers for kernel context switches. +// Don't need to save all the %fs etc. segment registers, +// because they are constant across kernel contexts. +// Save all the regular registers so we don't need to care +// which are caller save, but not the return register %eax. +// (Not saving %eax just simplifies the switching code.) +// The layout of context must match code in switch.S. +struct context { + uint32_t eip; + uint32_t esp; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t esi; + uint32_t edi; + uint32_t ebp; +}; + +#define PROC_NAME_LEN 50 +#define MAX_PROCESS 4096 +#define MAX_PID (MAX_PROCESS * 2) + +extern list_entry_t proc_list; + +struct proc_struct { + enum proc_state state; // Process state + int pid; // Process ID + int runs; // the running times of Proces + uintptr_t kstack; // Process kernel stack + volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + struct proc_struct *parent; // the parent process + struct mm_struct *mm; // Process's memory management field + struct context context; // Switch here to run process + struct trapframe *tf; // Trap frame for current interrupt + uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + uint32_t flags; // Process flag + char name[PROC_NAME_LEN + 1]; // Process name + list_entry_t list_link; // Process link list + list_entry_t hash_link; // Process hash list + int exit_code; // exit code (be sent to parent proc) + uint32_t wait_state; // waiting state + struct proc_struct *cptr, *yptr, *optr; // relations between processes + struct run_queue *rq; // running queue contains Process + list_entry_t run_link; // the entry linked in run queue + int time_slice; // time slice for occupying the CPU + skew_heap_entry_t lab6_run_pool; // FOR LAB6 ONLY: the entry in the run pool + uint32_t lab6_stride; // FOR LAB6 ONLY: the current stride of the process + uint32_t lab6_priority; // FOR LAB6 ONLY: the priority of process, set by lab6_set_priority(uint32_t) +}; + +#define PF_EXITING 0x00000001 // getting shutdown + +#define WT_INTERRUPTED 0x80000000 // the wait state could be interrupted +#define WT_CHILD (0x00000001 | WT_INTERRUPTED) // wait child process +#define WT_KSEM 0x00000100 // wait kernel semaphore +#define WT_TIMER (0x00000002 | WT_INTERRUPTED) // wait timer + +#define le2proc(le, member) \ + to_struct((le), struct proc_struct, member) + +extern struct proc_struct *idleproc, *initproc, *current; + +void proc_init(void); +void proc_run(struct proc_struct *proc); +int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags); + +char *set_proc_name(struct proc_struct *proc, const char *name); +char *get_proc_name(struct proc_struct *proc); +void cpu_idle(void) __attribute__((noreturn)); + +struct proc_struct *find_proc(int pid); +int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf); +int do_exit(int error_code); +int do_yield(void); +int do_execve(const char *name, size_t len, unsigned char *binary, size_t size); +int do_wait(int pid, int *code_store); +int do_kill(int pid); +//FOR LAB6, set the process's priority (bigger value will get more CPU time) +void lab6_set_priority(uint32_t priority); +int do_sleep(unsigned int time); +#endif /* !__KERN_PROCESS_PROC_H__ */ + diff --git a/code/lab7/kern/process/switch.S b/code/lab7/kern/process/switch.S new file mode 100644 index 0000000..27b4c8c --- /dev/null +++ b/code/lab7/kern/process/switch.S @@ -0,0 +1,30 @@ +.text +.globl switch_to +switch_to: # switch_to(from, to) + + # save from's registers + movl 4(%esp), %eax # eax points to from + popl 0(%eax) # save eip !popl + movl %esp, 4(%eax) + movl %ebx, 8(%eax) + movl %ecx, 12(%eax) + movl %edx, 16(%eax) + movl %esi, 20(%eax) + movl %edi, 24(%eax) + movl %ebp, 28(%eax) + + # restore to's registers + movl 4(%esp), %eax # not 8(%esp): popped return address already + # eax now points to to + movl 28(%eax), %ebp + movl 24(%eax), %edi + movl 20(%eax), %esi + movl 16(%eax), %edx + movl 12(%eax), %ecx + movl 8(%eax), %ebx + movl 4(%eax), %esp + + pushl 0(%eax) # push eip + + ret + diff --git a/code/lab7/kern/schedule/default_sched.c b/code/lab7/kern/schedule/default_sched.c new file mode 100644 index 0000000..2316990 --- /dev/null +++ b/code/lab7/kern/schedule/default_sched.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +static void +RR_init(struct run_queue *rq) { + list_init(&(rq->run_list)); + rq->proc_num = 0; +} + +static void +RR_enqueue(struct run_queue *rq, struct proc_struct *proc) { + assert(list_empty(&(proc->run_link))); + list_add_before(&(rq->run_list), &(proc->run_link)); + if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) { + proc->time_slice = rq->max_time_slice; + } + proc->rq = rq; + rq->proc_num ++; +} + +static void +RR_dequeue(struct run_queue *rq, struct proc_struct *proc) { + assert(!list_empty(&(proc->run_link)) && proc->rq == rq); + list_del_init(&(proc->run_link)); + rq->proc_num --; +} + +static struct proc_struct * +RR_pick_next(struct run_queue *rq) { + list_entry_t *le = list_next(&(rq->run_list)); + if (le != &(rq->run_list)) { + return le2proc(le, run_link); + } + return NULL; +} + +static void +RR_proc_tick(struct run_queue *rq, struct proc_struct *proc) { + if (proc->time_slice > 0) { + proc->time_slice --; + } + if (proc->time_slice == 0) { + proc->need_resched = 1; + } +} + +struct sched_class default_sched_class = { + .name = "RR_scheduler", + .init = RR_init, + .enqueue = RR_enqueue, + .dequeue = RR_dequeue, + .pick_next = RR_pick_next, + .proc_tick = RR_proc_tick, +}; + diff --git a/code/lab7/kern/schedule/default_sched.h b/code/lab7/kern/schedule/default_sched.h new file mode 100644 index 0000000..2f21fbd --- /dev/null +++ b/code/lab7/kern/schedule/default_sched.h @@ -0,0 +1,9 @@ +#ifndef __KERN_SCHEDULE_SCHED_RR_H__ +#define __KERN_SCHEDULE_SCHED_RR_H__ + +#include + +extern struct sched_class default_sched_class; + +#endif /* !__KERN_SCHEDULE_SCHED_RR_H__ */ + diff --git a/code/lab7/kern/schedule/default_sched_stride_c b/code/lab7/kern/schedule/default_sched_stride_c new file mode 100644 index 0000000..7075653 --- /dev/null +++ b/code/lab7/kern/schedule/default_sched_stride_c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +#define USE_SKEW_HEAP 1 + +/* You should define the BigStride constant here*/ +/* LAB6: YOUR CODE */ +#define BIG_STRIDE /* you should give a value, and is ??? */ + +/* The compare function for two skew_heap_node_t's and the + * corresponding procs*/ +static int +proc_stride_comp_f(void *a, void *b) +{ + struct proc_struct *p = le2proc(a, lab6_run_pool); + struct proc_struct *q = le2proc(b, lab6_run_pool); + int32_t c = p->lab6_stride - q->lab6_stride; + if (c > 0) return 1; + else if (c == 0) return 0; + else return -1; +} + +/* + * stride_init initializes the run-queue rq with correct assignment for + * member variables, including: + * + * - run_list: should be a empty list after initialization. + * - lab6_run_pool: NULL + * - proc_num: 0 + * - max_time_slice: no need here, the variable would be assigned by the caller. + * + * hint: see libs/list.h for routines of the list structures. + */ +static void +stride_init(struct run_queue *rq) { + /* LAB6: YOUR CODE + * (1) init the ready process list: rq->run_list + * (2) init the run pool: rq->lab6_run_pool + * (3) set number of process: rq->proc_num to 0 + */ +} + +/* + * stride_enqueue inserts the process ``proc'' into the run-queue + * ``rq''. The procedure should verify/initialize the relevant members + * of ``proc'', and then put the ``lab6_run_pool'' node into the + * queue(since we use priority queue here). The procedure should also + * update the meta date in ``rq'' structure. + * + * proc->time_slice denotes the time slices allocation for the + * process, which should set to rq->max_time_slice. + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static void +stride_enqueue(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE + * (1) insert the proc into rq correctly + * NOTICE: you can use skew_heap or list. Important functions + * skew_heap_insert: insert a entry into skew_heap + * list_add_before: insert a entry into the last of list + * (2) recalculate proc->time_slice + * (3) set proc->rq pointer to rq + * (4) increase rq->proc_num + */ +} + +/* + * stride_dequeue removes the process ``proc'' from the run-queue + * ``rq'', the operation would be finished by the skew_heap_remove + * operations. Remember to update the ``rq'' structure. + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static void +stride_dequeue(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE + * (1) remove the proc from rq correctly + * NOTICE: you can use skew_heap or list. Important functions + * skew_heap_remove: remove a entry from skew_heap + * list_del_init: remove a entry from the list + */ +} +/* + * stride_pick_next pick the element from the ``run-queue'', with the + * minimum value of stride, and returns the corresponding process + * pointer. The process pointer would be calculated by macro le2proc, + * see kern/process/proc.h for definition. Return NULL if + * there is no process in the queue. + * + * When one proc structure is selected, remember to update the stride + * property of the proc. (stride += BIG_STRIDE / priority) + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static struct proc_struct * +stride_pick_next(struct run_queue *rq) { + /* LAB6: YOUR CODE + * (1) get a proc_struct pointer p with the minimum value of stride + (1.1) If using skew_heap, we can use le2proc get the p from rq->lab6_run_poll + (1.2) If using list, we have to search list to find the p with minimum stride value + * (2) update p;s stride value: p->lab6_stride + * (3) return p + */ +} + +/* + * stride_proc_tick works with the tick event of current process. You + * should check whether the time slices for current process is + * exhausted and update the proc struct ``proc''. proc->time_slice + * denotes the time slices left for current + * process. proc->need_resched is the flag variable for process + * switching. + */ +static void +stride_proc_tick(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE */ +} + +struct sched_class default_sched_class = { + .name = "stride_scheduler", + .init = stride_init, + .enqueue = stride_enqueue, + .dequeue = stride_dequeue, + .pick_next = stride_pick_next, + .proc_tick = stride_proc_tick, +}; diff --git a/code/lab7/kern/schedule/sched.c b/code/lab7/kern/schedule/sched.c new file mode 100644 index 0000000..e272635 --- /dev/null +++ b/code/lab7/kern/schedule/sched.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include + +static list_entry_t timer_list; + +static struct sched_class *sched_class; + +static struct run_queue *rq; + +static inline void +sched_class_enqueue(struct proc_struct *proc) { + if (proc != idleproc) { + sched_class->enqueue(rq, proc); + } +} + +static inline void +sched_class_dequeue(struct proc_struct *proc) { + sched_class->dequeue(rq, proc); +} + +static inline struct proc_struct * +sched_class_pick_next(void) { + return sched_class->pick_next(rq); +} + +static void +sched_class_proc_tick(struct proc_struct *proc) { + if (proc != idleproc) { + sched_class->proc_tick(rq, proc); + } + else { + proc->need_resched = 1; + } +} + +static struct run_queue __rq; + +void +sched_init(void) { + list_init(&timer_list); + + sched_class = &default_sched_class; + + rq = &__rq; + rq->max_time_slice = 20; + sched_class->init(rq); + + cprintf("sched class: %s\n", sched_class->name); +} + +void +wakeup_proc(struct proc_struct *proc) { + assert(proc->state != PROC_ZOMBIE); + bool intr_flag; + local_intr_save(intr_flag); + { + if (proc->state != PROC_RUNNABLE) { + proc->state = PROC_RUNNABLE; + proc->wait_state = 0; + if (proc != current) { + sched_class_enqueue(proc); + } + } + else { + warn("wakeup runnable process.\n"); + } + } + local_intr_restore(intr_flag); +} + +void +schedule(void) { + bool intr_flag; + struct proc_struct *next; + local_intr_save(intr_flag); + { + current->need_resched = 0; + if (current->state == PROC_RUNNABLE) { + sched_class_enqueue(current); + } + if ((next = sched_class_pick_next()) != NULL) { + sched_class_dequeue(next); + } + if (next == NULL) { + next = idleproc; + } + next->runs ++; + if (next != current) { + proc_run(next); + } + } + local_intr_restore(intr_flag); +} + +void +add_timer(timer_t *timer) { + bool intr_flag; + local_intr_save(intr_flag); + { + assert(timer->expires > 0 && timer->proc != NULL); + assert(list_empty(&(timer->timer_link))); + list_entry_t *le = list_next(&timer_list); + while (le != &timer_list) { + timer_t *next = le2timer(le, timer_link); + if (timer->expires < next->expires) { + next->expires -= timer->expires; + break; + } + timer->expires -= next->expires; + le = list_next(le); + } + list_add_before(le, &(timer->timer_link)); + } + local_intr_restore(intr_flag); +} + +void +del_timer(timer_t *timer) { + bool intr_flag; + local_intr_save(intr_flag); + { + if (!list_empty(&(timer->timer_link))) { + if (timer->expires != 0) { + list_entry_t *le = list_next(&(timer->timer_link)); + if (le != &timer_list) { + timer_t *next = le2timer(le, timer_link); + next->expires += timer->expires; + } + } + list_del_init(&(timer->timer_link)); + } + } + local_intr_restore(intr_flag); +} + +void +run_timer_list(void) { + bool intr_flag; + local_intr_save(intr_flag); + { + list_entry_t *le = list_next(&timer_list); + if (le != &timer_list) { + timer_t *timer = le2timer(le, timer_link); + assert(timer->expires != 0); + timer->expires --; + while (timer->expires == 0) { + le = list_next(le); + struct proc_struct *proc = timer->proc; + if (proc->wait_state != 0) { + assert(proc->wait_state & WT_INTERRUPTED); + } + else { + warn("process %d's wait_state == 0.\n", proc->pid); + } + wakeup_proc(proc); + del_timer(timer); + if (le == &timer_list) { + break; + } + timer = le2timer(le, timer_link); + } + } + sched_class_proc_tick(current); + } + local_intr_restore(intr_flag); +} diff --git a/code/lab7/kern/schedule/sched.h b/code/lab7/kern/schedule/sched.h new file mode 100644 index 0000000..c83a776 --- /dev/null +++ b/code/lab7/kern/schedule/sched.h @@ -0,0 +1,70 @@ +#ifndef __KERN_SCHEDULE_SCHED_H__ +#define __KERN_SCHEDULE_SCHED_H__ + +#include +#include +#include + +struct proc_struct; + +typedef struct { + unsigned int expires; + struct proc_struct *proc; + list_entry_t timer_link; +} timer_t; + +#define le2timer(le, member) \ +to_struct((le), timer_t, member) + +static inline timer_t * +timer_init(timer_t *timer, struct proc_struct *proc, int expires) { + timer->expires = expires; + timer->proc = proc; + list_init(&(timer->timer_link)); + return timer; +} + +struct run_queue; + +// The introduction of scheduling classes is borrrowed from Linux, and makes the +// core scheduler quite extensible. These classes (the scheduler modules) encapsulate +// the scheduling policies. +struct sched_class { + // the name of sched_class + const char *name; + // Init the run queue + void (*init)(struct run_queue *rq); + // put the proc into runqueue, and this function must be called with rq_lock + void (*enqueue)(struct run_queue *rq, struct proc_struct *proc); + // get the proc out runqueue, and this function must be called with rq_lock + void (*dequeue)(struct run_queue *rq, struct proc_struct *proc); + // choose the next runnable task + struct proc_struct *(*pick_next)(struct run_queue *rq); + // dealer of the time-tick + void (*proc_tick)(struct run_queue *rq, struct proc_struct *proc); + /* for SMP support in the future + * load_balance + * void (*load_balance)(struct rq* rq); + * get some proc from this rq, used in load_balance, + * return value is the num of gotten proc + * int (*get_proc)(struct rq* rq, struct proc* procs_moved[]); + */ +}; + +struct run_queue { + list_entry_t run_list; + unsigned int proc_num; + int max_time_slice; + // For LAB6 ONLY + skew_heap_entry_t *lab6_run_pool; +}; + +void sched_init(void); +void wakeup_proc(struct proc_struct *proc); +void schedule(void); +void add_timer(timer_t *timer); +void del_timer(timer_t *timer); +void run_timer_list(void); + +#endif /* !__KERN_SCHEDULE_SCHED_H__ */ + diff --git a/code/lab7/kern/sync/check_sync.c b/code/lab7/kern/sync/check_sync.c new file mode 100644 index 0000000..9d48940 --- /dev/null +++ b/code/lab7/kern/sync/check_sync.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include + +#define N 5 /* 哲学家数目 */ +#define LEFT (i-1+N)%N /* i的左邻号码 */ +#define RIGHT (i+1)%N /* i的右邻号码 */ +#define THINKING 0 /* 哲学家正在思考 */ +#define HUNGRY 1 /* 哲学家想取得叉子 */ +#define EATING 2 /* 哲学家正在吃面 */ +#define TIMES 4 /* 吃4次饭 */ +#define SLEEP_TIME 10 + +//---------- philosophers problem using semaphore ---------------------- +int state_sema[N]; /* 记录每个人状态的数组 */ +/* 信号量是一个特殊的整型变量 */ +semaphore_t mutex; /* 临界区互斥 */ +semaphore_t s[N]; /* 每个哲学家一个信号量 */ + +struct proc_struct *philosopher_proc_sema[N]; + +void phi_test_sema(i) /* i:哲学家号码从0到N-1 */ +{ + if(state_sema[i]==HUNGRY&&state_sema[LEFT]!=EATING + &&state_sema[RIGHT]!=EATING) + { + state_sema[i]=EATING; + up(&s[i]); + } +} + +void phi_take_forks_sema(int i) /* i:哲学家号码从0到N-1 */ +{ + down(&mutex); /* 进入临界区 */ + state_sema[i]=HUNGRY; /* 记录下哲学家i饥饿的事实 */ + phi_test_sema(i); /* 试图得到两只叉子 */ + up(&mutex); /* 离开临界区 */ + down(&s[i]); /* 如果得不到叉子就阻塞 */ +} + +void phi_put_forks_sema(int i) /* i:哲学家号码从0到N-1 */ +{ + down(&mutex); /* 进入临界区 */ + state_sema[i]=THINKING; /* 哲学家进餐结束 */ + phi_test_sema(LEFT); /* 看一下左邻居现在是否能进餐 */ + phi_test_sema(RIGHT); /* 看一下右邻居现在是否能进餐 */ + up(&mutex); /* 离开临界区 */ +} + +int philosopher_using_semaphore(void * arg) /* i:哲学家号码,从0到N-1 */ +{ + int i, iter=0; + i=(int)arg; + cprintf("I am No.%d philosopher_sema\n",i); + while(iter++cv[i]) ; + } +} + + +void phi_take_forks_condvar(int i) { + down(&(mtp->mutex)); +//--------into routine in monitor-------------- + // LAB7 EXERCISE1: YOUR CODE + // I am hungry + // try to get fork +//--------leave routine in monitor-------------- + if(mtp->next_count>0) + up(&(mtp->next)); + else + up(&(mtp->mutex)); +} + +void phi_put_forks_condvar(int i) { + down(&(mtp->mutex)); + +//--------into routine in monitor-------------- + // LAB7 EXERCISE1: YOUR CODE + // I ate over + // test left and right neighbors +//--------leave routine in monitor-------------- + if(mtp->next_count>0) + up(&(mtp->next)); + else + up(&(mtp->mutex)); +} + +//---------- philosophers using monitor (condition variable) ---------------------- +int philosopher_using_condvar(void * arg) { /* arg is the No. of philosopher 0~N-1*/ + + int i, iter=0; + i=(int)arg; + cprintf("I am No.%d philosopher_condvar\n",i); + while(iter++ +#include +#include +#include + + +// Initialize monitor. +void +monitor_init (monitor_t * mtp, size_t num_cv) { + int i; + assert(num_cv>0); + mtp->next_count = 0; + mtp->cv = NULL; + sem_init(&(mtp->mutex), 1); //unlocked + sem_init(&(mtp->next), 0); + mtp->cv =(condvar_t *) kmalloc(sizeof(condvar_t)*num_cv); + assert(mtp->cv!=NULL); + for(i=0; icv[i].count=0; + sem_init(&(mtp->cv[i].sem),0); + mtp->cv[i].owner=mtp; + } +} + +// Unlock one of threads waiting on the condition variable. +void +cond_signal (condvar_t *cvp) { + //LAB7 EXERCISE1: YOUR CODE + cprintf("cond_signal begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); + /* + * cond_signal(cv) { + * if(cv.count>0) { + * mt.next_count ++; + * signal(cv.sem); + * wait(mt.next); + * mt.next_count--; + * } + * } + */ + cprintf("cond_signal end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); +} + +// Suspend calling thread on a condition variable waiting for condition Atomically unlocks +// mutex and suspends calling thread on conditional variable after waking up locks mutex. Notice: mp is mutex semaphore for monitor's procedures +void +cond_wait (condvar_t *cvp) { + //LAB7 EXERCISE1: YOUR CODE + cprintf("cond_wait begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); + /* + * cv.count ++; + * if(mt.next_count>0) + * signal(mt.next) + * else + * signal(mt.mutex); + * wait(cv.sem); + * cv.count --; + */ + cprintf("cond_wait end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); +} diff --git a/code/lab7/kern/sync/monitor.h b/code/lab7/kern/sync/monitor.h new file mode 100644 index 0000000..39b9610 --- /dev/null +++ b/code/lab7/kern/sync/monitor.h @@ -0,0 +1,90 @@ +#ifndef __KERN_SYNC_MONITOR_CONDVAR_H__ +#define __KERN_SYNC_MOINTOR_CONDVAR_H__ + +#include +/* In [OS CONCEPT] 7.7 section, the accurate define and approximate implementation of MONITOR was introduced. + * INTRODUCTION: + * Monitors were invented by C. A. R. Hoare and Per Brinch Hansen, and were first implemented in Brinch Hansen's + * Concurrent Pascal language. Generally, a monitor is a language construct and the compiler usually enforces mutual exclusion. Compare this with semaphores, which are usually an OS construct. + * DEFNIE & CHARACTERISTIC: + * A monitor is a collection of procedures, variables, and data structures grouped together. + * Processes can call the monitor procedures but cannot access the internal data structures. + * Only one process at a time may be be active in a monitor. + * Condition variables allow for blocking and unblocking. + * cv.wait() blocks a process. + * The process is said to be waiting for (or waiting on) the condition variable cv. + * cv.signal() (also called cv.notify) unblocks a process waiting for the condition variable cv. + * When this occurs, we need to still require that only one process is active in the monitor. This can be done in several ways: + * on some systems the old process (the one executing the signal) leaves the monitor and the new one enters + * on some systems the signal must be the last statement executed inside the monitor. + * on some systems the old process will block until the monitor is available again. + * on some systems the new process (the one unblocked by the signal) will remain blocked until the monitor is available again. + * If a condition variable is signaled with nobody waiting, the signal is lost. Compare this with semaphores, in which a signal will allow a process that executes a wait in the future to no block. + * You should not think of a condition variable as a variable in the traditional sense. + * It does not have a value. + * Think of it as an object in the OOP sense. + * It has two methods, wait and signal that manipulate the calling process. + * IMPLEMENTATION: + * monitor mt { + * ----------------variable------------------ + * semaphore mutex; + * semaphore next; + * int next_count; + * condvar {int count, sempahore sem} cv[N]; + * other variables in mt; + * --------condvar wait/signal--------------- + * cond_wait (cv) { + * cv.count ++; + * if(mt.next_count>0) + * signal(mt.next) + * else + * signal(mt.mutex); + * wait(cv.sem); + * cv.count --; + * } + * + * cond_signal(cv) { + * if(cv.count>0) { + * mt.next_count ++; + * signal(cv.sem); + * wait(mt.next); + * mt.next_count--; + * } + * } + * --------routines in monitor--------------- + * routineA_in_mt () { + * wait(mt.mutex); + * ... + * real body of routineA + * ... + * if(next_count>0) + * signal(mt.next); + * else + * signal(mt.mutex); + * } + */ + +typedef struct monitor monitor_t; + +typedef struct condvar{ + semaphore_t sem; // the sem semaphore is used to down the waiting proc, and the signaling proc should up the waiting proc + int count; // the number of waiters on condvar + monitor_t * owner; // the owner(monitor) of this condvar +} condvar_t; + +typedef struct monitor{ + semaphore_t mutex; // the mutex lock for going into the routines in monitor, should be initialized to 1 + semaphore_t next; // the next semaphore is used to down the signaling proc itself, and the other OR wakeuped waiting proc should wake up the sleeped signaling proc. + int next_count; // the number of of sleeped signaling proc + condvar_t *cv; // the condvars in monitor +} monitor_t; + +// Initialize variables in monitor. +void monitor_init (monitor_t *cvp, size_t num_cv); +// Unlock one of threads waiting on the condition variable. +void cond_signal (condvar_t *cvp); +// Suspend calling thread on a condition variable waiting for condition atomically unlock mutex in monitor, +// and suspends calling thread on conditional variable after waking up locks mutex. +void cond_wait (condvar_t *cvp); + +#endif /* !__KERN_SYNC_MONITOR_CONDVAR_H__ */ diff --git a/code/lab7/kern/sync/sem.c b/code/lab7/kern/sync/sem.c new file mode 100644 index 0000000..62c81db --- /dev/null +++ b/code/lab7/kern/sync/sem.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +void +sem_init(semaphore_t *sem, int value) { + sem->value = value; + wait_queue_init(&(sem->wait_queue)); +} + +static __noinline void __up(semaphore_t *sem, uint32_t wait_state) { + bool intr_flag; + local_intr_save(intr_flag); + { + wait_t *wait; + if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) { + sem->value ++; + } + else { + assert(wait->proc->wait_state == wait_state); + wakeup_wait(&(sem->wait_queue), wait, wait_state, 1); + } + } + local_intr_restore(intr_flag); +} + +static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) { + bool intr_flag; + local_intr_save(intr_flag); + if (sem->value > 0) { + sem->value --; + local_intr_restore(intr_flag); + return 0; + } + wait_t __wait, *wait = &__wait; + wait_current_set(&(sem->wait_queue), wait, wait_state); + local_intr_restore(intr_flag); + + schedule(); + + local_intr_save(intr_flag); + wait_current_del(&(sem->wait_queue), wait); + local_intr_restore(intr_flag); + + if (wait->wakeup_flags != wait_state) { + return wait->wakeup_flags; + } + return 0; +} + +void +up(semaphore_t *sem) { + __up(sem, WT_KSEM); +} + +void +down(semaphore_t *sem) { + uint32_t flags = __down(sem, WT_KSEM); + assert(flags == 0); +} + +bool +try_down(semaphore_t *sem) { + bool intr_flag, ret = 0; + local_intr_save(intr_flag); + if (sem->value > 0) { + sem->value --, ret = 1; + } + local_intr_restore(intr_flag); + return ret; +} + diff --git a/code/lab7/kern/sync/sem.h b/code/lab7/kern/sync/sem.h new file mode 100644 index 0000000..4450fe7 --- /dev/null +++ b/code/lab7/kern/sync/sem.h @@ -0,0 +1,19 @@ +#ifndef __KERN_SYNC_SEM_H__ +#define __KERN_SYNC_SEM_H__ + +#include +#include +#include + +typedef struct { + int value; + wait_queue_t wait_queue; +} semaphore_t; + +void sem_init(semaphore_t *sem, int value); +void up(semaphore_t *sem); +void down(semaphore_t *sem); +bool try_down(semaphore_t *sem); + +#endif /* !__KERN_SYNC_SEM_H__ */ + diff --git a/code/lab7/kern/sync/sync.h b/code/lab7/kern/sync/sync.h new file mode 100644 index 0000000..98215f1 --- /dev/null +++ b/code/lab7/kern/sync/sync.h @@ -0,0 +1,31 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab7/kern/sync/wait.c b/code/lab7/kern/sync/wait.c new file mode 100644 index 0000000..6aea172 --- /dev/null +++ b/code/lab7/kern/sync/wait.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +void +wait_init(wait_t *wait, struct proc_struct *proc) { + wait->proc = proc; + wait->wakeup_flags = WT_INTERRUPTED; + list_init(&(wait->wait_link)); +} + +void +wait_queue_init(wait_queue_t *queue) { + list_init(&(queue->wait_head)); +} + +void +wait_queue_add(wait_queue_t *queue, wait_t *wait) { + assert(list_empty(&(wait->wait_link)) && wait->proc != NULL); + wait->wait_queue = queue; + list_add_before(&(queue->wait_head), &(wait->wait_link)); +} + +void +wait_queue_del(wait_queue_t *queue, wait_t *wait) { + assert(!list_empty(&(wait->wait_link)) && wait->wait_queue == queue); + list_del_init(&(wait->wait_link)); +} + +wait_t * +wait_queue_next(wait_queue_t *queue, wait_t *wait) { + assert(!list_empty(&(wait->wait_link)) && wait->wait_queue == queue); + list_entry_t *le = list_next(&(wait->wait_link)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +wait_t * +wait_queue_prev(wait_queue_t *queue, wait_t *wait) { + assert(!list_empty(&(wait->wait_link)) && wait->wait_queue == queue); + list_entry_t *le = list_prev(&(wait->wait_link)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +wait_t * +wait_queue_first(wait_queue_t *queue) { + list_entry_t *le = list_next(&(queue->wait_head)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +wait_t * +wait_queue_last(wait_queue_t *queue) { + list_entry_t *le = list_prev(&(queue->wait_head)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +bool +wait_queue_empty(wait_queue_t *queue) { + return list_empty(&(queue->wait_head)); +} + +bool +wait_in_queue(wait_t *wait) { + return !list_empty(&(wait->wait_link)); +} + +void +wakeup_wait(wait_queue_t *queue, wait_t *wait, uint32_t wakeup_flags, bool del) { + if (del) { + wait_queue_del(queue, wait); + } + wait->wakeup_flags = wakeup_flags; + wakeup_proc(wait->proc); +} + +void +wakeup_first(wait_queue_t *queue, uint32_t wakeup_flags, bool del) { + wait_t *wait; + if ((wait = wait_queue_first(queue)) != NULL) { + wakeup_wait(queue, wait, wakeup_flags, del); + } +} + +void +wakeup_queue(wait_queue_t *queue, uint32_t wakeup_flags, bool del) { + wait_t *wait; + if ((wait = wait_queue_first(queue)) != NULL) { + if (del) { + do { + wakeup_wait(queue, wait, wakeup_flags, 1); + } while ((wait = wait_queue_first(queue)) != NULL); + } + else { + do { + wakeup_wait(queue, wait, wakeup_flags, 0); + } while ((wait = wait_queue_next(queue, wait)) != NULL); + } + } +} + +void +wait_current_set(wait_queue_t *queue, wait_t *wait, uint32_t wait_state) { + assert(current != NULL); + wait_init(wait, current); + current->state = PROC_SLEEPING; + current->wait_state = wait_state; + wait_queue_add(queue, wait); +} + diff --git a/code/lab7/kern/sync/wait.h b/code/lab7/kern/sync/wait.h new file mode 100644 index 0000000..46758b7 --- /dev/null +++ b/code/lab7/kern/sync/wait.h @@ -0,0 +1,48 @@ +#ifndef __KERN_SYNC_WAIT_H__ +#define __KERN_SYNC_WAIT_H__ + +#include + +typedef struct { + list_entry_t wait_head; +} wait_queue_t; + +struct proc_struct; + +typedef struct { + struct proc_struct *proc; + uint32_t wakeup_flags; + wait_queue_t *wait_queue; + list_entry_t wait_link; +} wait_t; + +#define le2wait(le, member) \ + to_struct((le), wait_t, member) + +void wait_init(wait_t *wait, struct proc_struct *proc); +void wait_queue_init(wait_queue_t *queue); +void wait_queue_add(wait_queue_t *queue, wait_t *wait); +void wait_queue_del(wait_queue_t *queue, wait_t *wait); + +wait_t *wait_queue_next(wait_queue_t *queue, wait_t *wait); +wait_t *wait_queue_prev(wait_queue_t *queue, wait_t *wait); +wait_t *wait_queue_first(wait_queue_t *queue); +wait_t *wait_queue_last(wait_queue_t *queue); + +bool wait_queue_empty(wait_queue_t *queue); +bool wait_in_queue(wait_t *wait); +void wakeup_wait(wait_queue_t *queue, wait_t *wait, uint32_t wakeup_flags, bool del); +void wakeup_first(wait_queue_t *queue, uint32_t wakeup_flags, bool del); +void wakeup_queue(wait_queue_t *queue, uint32_t wakeup_flags, bool del); + +void wait_current_set(wait_queue_t *queue, wait_t *wait, uint32_t wait_state); + +#define wait_current_del(queue, wait) \ + do { \ + if (wait_in_queue(wait)) { \ + wait_queue_del(queue, wait); \ + } \ + } while (0) + +#endif /* !__KERN_SYNC_WAIT_H__ */ + diff --git a/code/lab7/kern/syscall/syscall.c b/code/lab7/kern/syscall/syscall.c new file mode 100644 index 0000000..6902e04 --- /dev/null +++ b/code/lab7/kern/syscall/syscall.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static int +sys_exit(uint32_t arg[]) { + int error_code = (int)arg[0]; + return do_exit(error_code); +} + +static int +sys_fork(uint32_t arg[]) { + struct trapframe *tf = current->tf; + uintptr_t stack = tf->tf_esp; + return do_fork(0, stack, tf); +} + +static int +sys_wait(uint32_t arg[]) { + int pid = (int)arg[0]; + int *store = (int *)arg[1]; + return do_wait(pid, store); +} + +static int +sys_exec(uint32_t arg[]) { + const char *name = (const char *)arg[0]; + size_t len = (size_t)arg[1]; + unsigned char *binary = (unsigned char *)arg[2]; + size_t size = (size_t)arg[3]; + return do_execve(name, len, binary, size); +} + +static int +sys_yield(uint32_t arg[]) { + return do_yield(); +} + +static int +sys_kill(uint32_t arg[]) { + int pid = (int)arg[0]; + return do_kill(pid); +} + +static int +sys_getpid(uint32_t arg[]) { + return current->pid; +} + +static int +sys_putc(uint32_t arg[]) { + int c = (int)arg[0]; + cputchar(c); + return 0; +} + +static int +sys_pgdir(uint32_t arg[]) { + print_pgdir(); + return 0; +} + +static uint32_t +sys_gettime(uint32_t arg[]) { + return (int)ticks; +} +static uint32_t +sys_lab6_set_priority(uint32_t arg[]) +{ + uint32_t priority = (uint32_t)arg[0]; + lab6_set_priority(priority); + return 0; +} + +static int +sys_sleep(uint32_t arg[]) { + unsigned int time = (unsigned int)arg[0]; + return do_sleep(time); +} + +static int (*syscalls[])(uint32_t arg[]) = { + [SYS_exit] sys_exit, + [SYS_fork] sys_fork, + [SYS_wait] sys_wait, + [SYS_exec] sys_exec, + [SYS_yield] sys_yield, + [SYS_kill] sys_kill, + [SYS_getpid] sys_getpid, + [SYS_putc] sys_putc, + [SYS_pgdir] sys_pgdir, + [SYS_gettime] sys_gettime, + [SYS_lab6_set_priority] sys_lab6_set_priority, + [SYS_sleep] sys_sleep, +}; + +#define NUM_SYSCALLS ((sizeof(syscalls)) / (sizeof(syscalls[0]))) + +void +syscall(void) { + struct trapframe *tf = current->tf; + uint32_t arg[5]; + int num = tf->tf_regs.reg_eax; + if (num >= 0 && num < NUM_SYSCALLS) { + if (syscalls[num] != NULL) { + arg[0] = tf->tf_regs.reg_edx; + arg[1] = tf->tf_regs.reg_ecx; + arg[2] = tf->tf_regs.reg_ebx; + arg[3] = tf->tf_regs.reg_edi; + arg[4] = tf->tf_regs.reg_esi; + tf->tf_regs.reg_eax = syscalls[num](arg); + return ; + } + } + print_trapframe(tf); + panic("undefined syscall %d, pid = %d, name = %s.\n", + num, current->pid, current->name); +} + diff --git a/code/lab7/kern/syscall/syscall.h b/code/lab7/kern/syscall/syscall.h new file mode 100644 index 0000000..a8fe843 --- /dev/null +++ b/code/lab7/kern/syscall/syscall.h @@ -0,0 +1,7 @@ +#ifndef __KERN_SYSCALL_SYSCALL_H__ +#define __KERN_SYSCALL_SYSCALL_H__ + +void syscall(void); + +#endif /* !__KERN_SYSCALL_SYSCALL_H__ */ + diff --git a/code/lab7/kern/trap/trap.c b/code/lab7/kern/trap/trap.c new file mode 100644 index 0000000..e8eb143 --- /dev/null +++ b/code/lab7/kern/trap/trap.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ + /* LAB5 YOUR CODE */ + //you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore + //so you should setup the syscall interrupt gate in here +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static inline void +print_pgfault(struct trapframe *tf) { + /* error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * bit 2 == 0 means kernel, 1 means user + * */ + cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), + (tf->tf_err & 4) ? 'U' : 'K', + (tf->tf_err & 2) ? 'W' : 'R', + (tf->tf_err & 1) ? "protection fault" : "no page found"); +} + +static int +pgfault_handler(struct trapframe *tf) { + extern struct mm_struct *check_mm_struct; + if(check_mm_struct !=NULL) { //used for test check_swap + print_pgfault(tf); + } + struct mm_struct *mm; + if (check_mm_struct != NULL) { + assert(current == idleproc); + mm = check_mm_struct; + } + else { + if (current == NULL) { + print_trapframe(tf); + print_pgfault(tf); + panic("unhandled page fault.\n"); + } + mm = current->mm; + } + return do_pgfault(mm, tf->tf_err, rcr2()); +} + +static volatile int in_swap_tick_event = 0; +extern struct mm_struct *check_mm_struct; + +static void +trap_dispatch(struct trapframe *tf) { + char c; + + int ret=0; + + switch (tf->tf_trapno) { + case T_PGFLT: //page fault + if ((ret = pgfault_handler(tf)) != 0) { + print_trapframe(tf); + if (current == NULL) { + panic("handle pgfault failed. ret=%d\n", ret); + } + else { + if (trap_in_kernel(tf)) { + panic("handle pgfault failed in kernel mode. ret=%d\n", ret); + } + cprintf("killed by kernel.\n"); + panic("handle user mode pgfault failed. ret=%d\n", ret); + do_exit(-E_KILLED); + } + } + break; + case T_SYSCALL: + syscall(); + break; + case IRQ_OFFSET + IRQ_TIMER: +#if 0 + LAB3 : If some page replacement algorithm need tick to change the priority of pages, + then you can add code here. +#endif + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + /* LAB5 YOUR CODE */ + /* you should upate you lab1 code (just add ONE or TWO lines of code): + * Every TICK_NUM cycle, you should set current process's current->need_resched = 1 + */ + + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + print_trapframe(tf); + if (current != NULL) { + cprintf("unhandled trap.\n"); + do_exit(-E_KILLED); + } + // in kernel, it must be a mistake + panic("unexpected trap in kernel.\n"); + + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + // used for previous projects + if (current == NULL) { + trap_dispatch(tf); + } + else { + // keep a trapframe chain in stack + struct trapframe *otf = current->tf; + current->tf = tf; + + bool in_kernel = trap_in_kernel(tf); + + trap_dispatch(tf); + + current->tf = otf; + if (!in_kernel) { + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + if (current->need_resched) { + schedule(); + } + } + } +} + diff --git a/code/lab7/kern/trap/trap.h b/code/lab7/kern/trap/trap.h new file mode 100644 index 0000000..e870a6f --- /dev/null +++ b/code/lab7/kern/trap/trap.h @@ -0,0 +1,89 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab7/kern/trap/trapentry.S b/code/lab7/kern/trap/trapentry.S new file mode 100644 index 0000000..3565ec8 --- /dev/null +++ b/code/lab7/kern/trap/trapentry.S @@ -0,0 +1,49 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + +.globl forkrets +forkrets: + # set stack to this new process's trapframe + movl 4(%esp), %esp + jmp __trapret diff --git a/code/lab7/kern/trap/vectors.S b/code/lab7/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab7/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab7/libs/atomic.h b/code/lab7/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab7/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab7/libs/defs.h b/code/lab7/libs/defs.h new file mode 100644 index 0000000..88f280e --- /dev/null +++ b/code/lab7/libs/defs.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab7/libs/elf.h b/code/lab7/libs/elf.h new file mode 100644 index 0000000..8678f10 --- /dev/null +++ b/code/lab7/libs/elf.h @@ -0,0 +1,48 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +/* values for Proghdr::p_type */ +#define ELF_PT_LOAD 1 + +/* flag bits for Proghdr::p_flags */ +#define ELF_PF_X 1 +#define ELF_PF_W 2 +#define ELF_PF_R 4 + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab7/libs/error.h b/code/lab7/libs/error.h new file mode 100644 index 0000000..ddad593 --- /dev/null +++ b/code/lab7/libs/error.h @@ -0,0 +1,33 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault +#define E_SWAP_FAULT 7 // SWAP READ/WRITE fault +#define E_INVAL_ELF 8 // Invalid elf file +#define E_KILLED 9 // Process is killed +#define E_PANIC 10 // Panic Failure +#define E_TIMEOUT 11 // Timeout +#define E_TOO_BIG 12 // Argument is Too Big +#define E_NO_DEV 13 // No such Device +#define E_NA_DEV 14 // Device Not Available +#define E_BUSY 15 // Device/File is Busy +#define E_NOENT 16 // No Such File or Directory +#define E_ISDIR 17 // Is a Directory +#define E_NOTDIR 18 // Not a Directory +#define E_XDEV 19 // Cross Device-Link +#define E_UNIMP 20 // Unimplemented Feature +#define E_SEEK 21 // Illegal Seek +#define E_MAX_OPEN 22 // Too Many Files are Open +#define E_EXISTS 23 // File/Directory Already Exists +#define E_NOTEMPTY 24 // Directory is Not Empty +/* the maximum allowed */ +#define MAXERROR 24 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab7/libs/hash.c b/code/lab7/libs/hash.c new file mode 100644 index 0000000..61bcd88 --- /dev/null +++ b/code/lab7/libs/hash.c @@ -0,0 +1,18 @@ +#include + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL + +/* * + * hash32 - generate a hash value in the range [0, 2^@bits - 1] + * @val: the input value + * @bits: the number of bits in a return value + * + * High bits are more random, so we use them. + * */ +uint32_t +hash32(uint32_t val, unsigned int bits) { + uint32_t hash = val * GOLDEN_RATIO_PRIME_32; + return (hash >> (32 - bits)); +} + diff --git a/code/lab7/libs/list.h b/code/lab7/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab7/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab7/libs/printfmt.c b/code/lab7/libs/printfmt.c new file mode 100644 index 0000000..a666a52 --- /dev/null +++ b/code/lab7/libs/printfmt.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", + [E_INVAL_ELF] "invalid elf file", + [E_KILLED] "process is killed", + [E_PANIC] "panic failure", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*), void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*), void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, putdat, "error %d", err); + } + else { + printfmt(putch, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat); + } + else { + putch(ch, putdat); + } + } + for (; width > 0; width --) { + putch(' ', putdat); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat); + putch('x', putdat); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab7/libs/rand.c b/code/lab7/libs/rand.c new file mode 100644 index 0000000..2a2c4e7 --- /dev/null +++ b/code/lab7/libs/rand.c @@ -0,0 +1,26 @@ +#include +#include + +static unsigned long long next = 1; + +/* * + * rand - returns a pseudo-random integer + * + * The rand() function return a value in the range [0, RAND_MAX]. + * */ +int +rand(void) { + next = (next * 0x5DEECE66DLL + 0xBLL) & ((1LL << 48) - 1); + unsigned long long result = (next >> 12); + return (int)do_div(result, RAND_MAX + 1); +} + +/* * + * srand - seed the random number generator with the given number + * @seed: the required seed number + * */ +void +srand(unsigned int seed) { + next = seed; +} + diff --git a/code/lab7/libs/skew_heap.h b/code/lab7/libs/skew_heap.h new file mode 100644 index 0000000..0c216b8 --- /dev/null +++ b/code/lab7/libs/skew_heap.h @@ -0,0 +1,87 @@ +#ifndef __LIBS_SKEW_HEAP_H__ +#define __LIBS_SKEW_HEAP_H__ + +struct skew_heap_entry { + struct skew_heap_entry *parent, *left, *right; +}; + +typedef struct skew_heap_entry skew_heap_entry_t; + +typedef int(*compare_f)(void *a, void *b); + +static inline void skew_heap_init(skew_heap_entry_t *a) __attribute__((always_inline)); +static inline skew_heap_entry_t *skew_heap_merge( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp); +static inline skew_heap_entry_t *skew_heap_insert( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) __attribute__((always_inline)); +static inline skew_heap_entry_t *skew_heap_remove( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) __attribute__((always_inline)); + +static inline void +skew_heap_init(skew_heap_entry_t *a) +{ + a->left = a->right = a->parent = NULL; +} + +static inline skew_heap_entry_t * +skew_heap_merge(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + if (a == NULL) return b; + else if (b == NULL) return a; + + skew_heap_entry_t *l, *r; + if (comp(a, b) == -1) + { + r = a->left; + l = skew_heap_merge(a->right, b, comp); + + a->left = l; + a->right = r; + if (l) l->parent = a; + + return a; + } + else + { + r = b->left; + l = skew_heap_merge(a, b->right, comp); + + b->left = l; + b->right = r; + if (l) l->parent = b; + + return b; + } +} + +static inline skew_heap_entry_t * +skew_heap_insert(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + skew_heap_init(b); + return skew_heap_merge(a, b, comp); +} + +static inline skew_heap_entry_t * +skew_heap_remove(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + skew_heap_entry_t *p = b->parent; + skew_heap_entry_t *rep = skew_heap_merge(b->left, b->right, comp); + if (rep) rep->parent = p; + + if (p) + { + if (p->left == b) + p->left = rep; + else p->right = rep; + return a; + } + else return rep; +} + +#endif /* !__LIBS_SKEW_HEAP_H__ */ diff --git a/code/lab7/libs/stdarg.h b/code/lab7/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab7/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab7/libs/stdio.h b/code/lab7/libs/stdio.h new file mode 100644 index 0000000..41e8b41 --- /dev/null +++ b/code/lab7/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *), void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *), void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab7/libs/stdlib.h b/code/lab7/libs/stdlib.h new file mode 100644 index 0000000..51878ef --- /dev/null +++ b/code/lab7/libs/stdlib.h @@ -0,0 +1,17 @@ +#ifndef __LIBS_STDLIB_H__ +#define __LIBS_STDLIB_H__ + +#include + +/* the largest number rand will return */ +#define RAND_MAX 2147483647UL + +/* libs/rand.c */ +int rand(void); +void srand(unsigned int seed); + +/* libs/hash.c */ +uint32_t hash32(uint32_t val, unsigned int bits); + +#endif /* !__LIBS_RAND_H__ */ + diff --git a/code/lab7/libs/string.c b/code/lab7/libs/string.c new file mode 100644 index 0000000..bcf1b1c --- /dev/null +++ b/code/lab7/libs/string.c @@ -0,0 +1,367 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab7/libs/string.h b/code/lab7/libs/string.h new file mode 100644 index 0000000..14d0aac --- /dev/null +++ b/code/lab7/libs/string.h @@ -0,0 +1,25 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab7/libs/unistd.h b/code/lab7/libs/unistd.h new file mode 100644 index 0000000..b4ceff9 --- /dev/null +++ b/code/lab7/libs/unistd.h @@ -0,0 +1,31 @@ +#ifndef __LIBS_UNISTD_H__ +#define __LIBS_UNISTD_H__ + +#define T_SYSCALL 0x80 + +/* syscall number */ +#define SYS_exit 1 +#define SYS_fork 2 +#define SYS_wait 3 +#define SYS_exec 4 +#define SYS_clone 5 +#define SYS_yield 10 +#define SYS_sleep 11 +#define SYS_kill 12 +#define SYS_gettime 17 +#define SYS_getpid 18 +#define SYS_brk 19 +#define SYS_mmap 20 +#define SYS_munmap 21 +#define SYS_shmem 22 +#define SYS_putc 30 +#define SYS_pgdir 31 +/* OLNY FOR LAB6 */ +#define SYS_lab6_set_priority 255 + +/* SYS_fork flags */ +#define CLONE_VM 0x00000100 // set if VM shared between processes +#define CLONE_THREAD 0x00000200 // thread group + +#endif /* !__LIBS_UNISTD_H__ */ + diff --git a/code/lab7/libs/x86.h b/code/lab7/libs/x86.h new file mode 100644 index 0000000..b29f671 --- /dev/null +++ b/code/lab7/libs/x86.h @@ -0,0 +1,302 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab7/tools/boot.ld b/code/lab7/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab7/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab7/tools/function.mk b/code/lab7/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab7/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab7/tools/gdbinit b/code/lab7/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab7/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab7/tools/grade.sh b/code/lab7/tools/grade.sh new file mode 100644 index 0000000..de10321 --- /dev/null +++ b/code/lab7/tools/grade.sh @@ -0,0 +1,636 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-prog ] [-Ddef...] [-check ] checkargs ... + tag= + prog= + check=check_regexps + while true; do + select= + case $1 in + -tag|-prog) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + if [ -z "$prog" ]; then + $make $makeopts touch > /dev/null 2>&1 + args="$defs" + else + if [ -z "$tag" ]; then + tag="$prog" + fi + args="build-$prog $defs" + fi + + build_run "$tag" "$args" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## swap image +swapimg=$(make_print swapimg) + +## set default qemu-options +qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback" + +## set break-function, default is readline +brkfun=readline + +default_check() { + pts=7 + check_regexps "$@" + + pts=3 + quick_check 'check output' \ + 'memory management: default_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' \ + 'check_slab() succeeded!' \ + 'check_vma_struct() succeeded!' \ + 'page fault at 0x00000100: K/W [no page found].' \ + 'check_pgfault() succeeded!' \ + 'check_vmm() succeeded.' \ + 'page fault at 0x00001000: K/W [no page found].' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'write Virt Page e in fifo_check_swap' \ + 'page fault at 0x00005000: K/W [no page found].' \ + 'page fault at 0x00001000: K/W [no page found]' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'check_swap() succeeded!' \ + '++ setup timer interrupts' +} + +## check now!! + +run_test -prog 'badsegment' -check default_check \ + - 'kernel_execve: pid = ., name = "badsegment".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000028' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'divzero' -check default_check \ + - 'kernel_execve: pid = ., name = "divzero".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x00000000 Divide error' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'softint' -check default_check \ + - 'kernel_execve: pid = ., name = "softint".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000072' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'faultread' -check default_check \ + - 'kernel_execve: pid = ., name = "faultread".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000004' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'faultreadkernel' -check default_check \ + - 'kernel_execve: pid = ., name = "faultreadkernel".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000005' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'hello' -check default_check \ + - 'kernel_execve: pid = ., name = "hello".*' \ + 'Hello world!!.' \ + - 'I am process .*' \ + 'hello pass.' + +run_test -prog 'testbss' -check default_check \ + - 'kernel_execve: pid = ., name = "testbss".*' \ + 'Making sure bss works right...' \ + 'Yes, good. Now doing a wild write off the end...' \ + 'testbss may pass.' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000006' \ + - ' eip 0x008.....' \ + 'killed by kernel.' \ + ! - 'user panic at .*' + +run_test -prog 'pgdir' -check default_check \ + - 'kernel_execve: pid = ., name = "pgdir".*' \ + - 'I am .*' \ + 'PDE(001) 00800000-00c00000 00400000 urw' \ + ' |-- PTE(00002) 00800000-00802000 00002000 ur-' \ + ' |-- PTE(00001) 00802000-00803000 00001000 urw' \ + 'PDE(001) afc00000-b0000000 00400000 urw' \ + ' |-- PTE(00004) afffc000-b0000000 00004000 urw' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'pgdir pass.' + +run_test -prog 'yield' -check default_check \ + - 'kernel_execve: pid = ., name = "yield".*' \ + 'Hello, I am process 2.' \ + - 'Back in process ., iteration 0.' \ + - 'Back in process ., iteration 1.' \ + - 'Back in process ., iteration 2.' \ + - 'Back in process ., iteration 3.' \ + - 'Back in process ., iteration 4.' \ + - 'All done in process .*' \ + 'yield pass.' + + +run_test -prog 'badarg' -check default_check \ + - 'kernel_execve: pid = ., name = "badarg".*' \ + 'fork ok.' \ + 'badarg pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'exit' -check default_check \ + - 'kernel_execve: pid = ., name = "exit".*' \ + 'I am the parent. Forking the child...' \ + 'I am the parent, waiting now..' \ + 'I am the child.' \ + - 'waitpid [0-9]+ ok\.' \ + 'exit pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'spin' -check default_check \ + - 'kernel_execve: pid = ., name = "spin".*' \ + 'I am the parent. Forking the child...' \ + 'I am the parent. Running the child...' \ + 'I am the child. spinning ...' \ + 'I am the parent. Killing the child...' \ + 'kill returns 0' \ + 'wait returns 0' \ + 'spin may pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'waitkill' -check default_check \ + - 'kernel_execve: pid = ., name = "waitkill".*' \ + 'wait child 1.' \ + 'child 2.' \ + 'child 1.' \ + 'kill parent ok.' \ + 'kill child1 ok.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=15 + +run_test -prog 'forktest' -check default_check \ + - 'kernel_execve: pid = ., name = "forktest".*' \ + 'I am child 31' \ + 'I am child 19' \ + 'I am child 13' \ + 'I am child 0' \ + 'forktest pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'fork claimed to work [0-9]+ times!' \ + ! 'wait stopped early' \ + ! 'wait got too many' \ + ! - 'user panic at .*' + +pts=10 +run_test -prog 'forktree' -check default_check \ + - 'kernel_execve: pid = ., name = "forktree".*' \ + - '....: I am '\'''\' \ + - '....: I am '\''0'\' \ + - '....: I am '\'''\' \ + - '....: I am '\''1'\' \ + - '....: I am '\''0'\' \ + - '....: I am '\''01'\' \ + - '....: I am '\''00'\' \ + - '....: I am '\''11'\' \ + - '....: I am '\''10'\' \ + - '....: I am '\''101'\' \ + - '....: I am '\''100'\' \ + - '....: I am '\''111'\' \ + - '....: I am '\''110'\' \ + - '....: I am '\''001'\' \ + - '....: I am '\''000'\' \ + - '....: I am '\''011'\' \ + - '....: I am '\''010'\' \ + - '....: I am '\''0101'\' \ + - '....: I am '\''0100'\' \ + - '....: I am '\''0111'\' \ + - '....: I am '\''0110'\' \ + - '....: I am '\''0001'\' \ + - '....: I am '\''0000'\' \ + - '....: I am '\''0011'\' \ + - '....: I am '\''0010'\' \ + - '....: I am '\''1101'\' \ + - '....: I am '\''1100'\' \ + - '....: I am '\''1111'\' \ + - '....: I am '\''1110'\' \ + - '....: I am '\''1001'\' \ + - '....: I am '\''1000'\' \ + - '....: I am '\''1011'\' \ + - '....: I am '\''1010'\' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' + +pts=20 +timeout=150 +run_test -prog 'priority' -check default_check \ + 'sched class: stride_scheduler' \ + - 'kernel_execve: pid = ., name = "priority".*' \ + 'main: fork ok,now need to wait pids.' \ + 'stride sched correct result: 1 2 3 4 5' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=20 +timeout=240 + +run_test -prog 'sleep' -check default_check \ + - 'kernel_execve: pid = ., name = "sleep".*' \ + 'sleep 1 x 100 slices.' \ + 'sleep 3 x 100 slices.' \ + 'sleep 7 x 100 slices.' \ + 'sleep 10 x 100 slices.' \ + - 'use 1... msecs.' \ + 'sleep pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! ' trap 0x0000000e Page Fault' \ + ! 'killed by kernel.' \ + ! - 'user panic at .*' + +pts=20 +timeout=240 +run_test -prog 'sleepkill' -check default_check \ + - 'kernel_execve: pid = ., name = "sleepkill".*' \ + 'sleepkill pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=40 +timeout=500 +run_test -prog 'matrix' -check default_check \ + 'Iter 1, No.0 philosopher_sema is thinking' \ + 'Iter 1, No.1 philosopher_sema is thinking' \ + 'Iter 1, No.2 philosopher_sema is thinking' \ + 'Iter 1, No.3 philosopher_sema is thinking' \ + 'Iter 1, No.4 philosopher_sema is thinking' \ + 'Iter 1, No.0 philosopher_sema is eating' \ + 'Iter 1, No.1 philosopher_sema is eating' \ + 'Iter 1, No.2 philosopher_sema is eating' \ + 'Iter 1, No.3 philosopher_sema is eating' \ + 'Iter 1, No.4 philosopher_sema is eating' \ + 'No.0 philosopher_sema quit' \ + 'No.1 philosopher_sema quit' \ + 'No.2 philosopher_sema quit' \ + 'No.3 philosopher_sema quit' \ + 'No.4 philosopher_sema quit' \ + 'Iter 1, No.0 philosopher_condvar is thinking' \ + 'Iter 1, No.1 philosopher_condvar is thinking' \ + 'Iter 1, No.2 philosopher_condvar is thinking' \ + 'Iter 1, No.3 philosopher_condvar is thinking' \ + 'Iter 1, No.4 philosopher_condvar is thinking' \ + 'Iter 1, No.0 philosopher_condvar is eating' \ + 'Iter 1, No.1 philosopher_condvar is eating' \ + 'Iter 1, No.2 philosopher_condvar is eating' \ + 'Iter 1, No.3 philosopher_condvar is eating' \ + 'Iter 1, No.4 philosopher_condvar is eating' \ + 'No.0 philosopher_condvar quit' \ + 'No.1 philosopher_condvar quit' \ + 'No.2 philosopher_condvar quit' \ + 'No.3 philosopher_condvar quit' \ + 'No.4 philosopher_condvar quit' \ + - 'kernel_execve: pid = ., name = "matrix".*' \ + 'fork ok.' \ + 'pid 13 done!.' \ + 'pid 17 done!.' \ + 'pid 23 done!.' \ + 'matrix pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +## print final-score +show_final + diff --git a/code/lab7/tools/kernel.ld b/code/lab7/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab7/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab7/tools/sign.c b/code/lab7/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab7/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab7/tools/user.ld b/code/lab7/tools/user.ld new file mode 100644 index 0000000..c73b6fa --- /dev/null +++ b/code/lab7/tools/user.ld @@ -0,0 +1,71 @@ +/* Simple linker script for ucore user-level programs. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS { + /* Load programs at this address: "." means the current address */ + . = 0x800020; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + + /* Place debugging symbols so that they can be found by + * the kernel debugger. + * Specifically, the four words at 0x200000 mark the beginning of + * the stabs, the end of the stabs, the beginning of the stabs + * string table, and the end of the stabs string table, respectively. + */ + + .stab_info 0x200000 : { + LONG(__STAB_BEGIN__); + LONG(__STAB_END__); + LONG(__STABSTR_BEGIN__); + LONG(__STABSTR_END__); + } + + .stab : { + __STAB_BEGIN__ = DEFINED(__STAB_BEGIN__) ? __STAB_BEGIN__ : .; + *(.stab); + __STAB_END__ = DEFINED(__STAB_END__) ? __STAB_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + __STABSTR_BEGIN__ = DEFINED(__STABSTR_BEGIN__) ? __STABSTR_BEGIN__ : .; + *(.stabstr); + __STABSTR_END__ = DEFINED(__STABSTR_END__) ? __STABSTR_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack .comment) + } +} diff --git a/code/lab7/tools/vector.c b/code/lab7/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab7/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab7/user/badarg.c b/code/lab7/user/badarg.c new file mode 100644 index 0000000..7b4ffad --- /dev/null +++ b/code/lab7/user/badarg.c @@ -0,0 +1,22 @@ +#include +#include + +int +main(void) { + int pid, exit_code; + if ((pid = fork()) == 0) { + cprintf("fork ok.\n"); + int i; + for (i = 0; i < 10; i ++) { + yield(); + } + exit(0xbeaf); + } + assert(pid > 0); + assert(waitpid(-1, NULL) != 0); + assert(waitpid(pid, (void *)0xC0000000) != 0); + assert(waitpid(pid, &exit_code) == 0 && exit_code == 0xbeaf); + cprintf("badarg pass.\n"); + return 0; +} + diff --git a/code/lab7/user/badsegment.c b/code/lab7/user/badsegment.c new file mode 100644 index 0000000..cdd9e99 --- /dev/null +++ b/code/lab7/user/badsegment.c @@ -0,0 +1,11 @@ +#include +#include + +/* try to load the kernel's TSS selector into the DS register */ + +int +main(void) { + asm volatile("movw $0x28,%ax; movw %ax,%ds"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/divzero.c b/code/lab7/user/divzero.c new file mode 100644 index 0000000..16c23d5 --- /dev/null +++ b/code/lab7/user/divzero.c @@ -0,0 +1,11 @@ +#include +#include + +int zero; + +int +main(void) { + cprintf("value is %d.\n", 1 / zero); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/exit.c b/code/lab7/user/exit.c new file mode 100644 index 0000000..c3ac5f8 --- /dev/null +++ b/code/lab7/user/exit.c @@ -0,0 +1,34 @@ +#include +#include + +int magic = -0x10384; + +int +main(void) { + int pid, code; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child.\n"); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + exit(magic); + } + else { + cprintf("I am parent, fork a child pid %d\n",pid); + } + assert(pid > 0); + cprintf("I am the parent, waiting now..\n"); + + assert(waitpid(pid, &code) == 0 && code == magic); + assert(waitpid(pid, &code) != 0 && wait() != 0); + cprintf("waitpid %d ok.\n", pid); + + cprintf("exit pass.\n"); + return 0; +} + diff --git a/code/lab7/user/faultread.c b/code/lab7/user/faultread.c new file mode 100644 index 0000000..6d292e1 --- /dev/null +++ b/code/lab7/user/faultread.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %8x from 0.\n", *(unsigned int *)0); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/faultreadkernel.c b/code/lab7/user/faultreadkernel.c new file mode 100644 index 0000000..53457f6 --- /dev/null +++ b/code/lab7/user/faultreadkernel.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %08x from 0xfac00000!\n", *(unsigned *)0xfac00000); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/forktest.c b/code/lab7/user/forktest.c new file mode 100644 index 0000000..3eda228 --- /dev/null +++ b/code/lab7/user/forktest.c @@ -0,0 +1,34 @@ +#include +#include + +const int max_child = 32; + +int +main(void) { + int n, pid; + for (n = 0; n < max_child; n ++) { + if ((pid = fork()) == 0) { + cprintf("I am child %d\n", n); + exit(0); + } + assert(pid > 0); + } + + if (n > max_child) { + panic("fork claimed to work %d times!\n", n); + } + + for (; n > 0; n --) { + if (wait() != 0) { + panic("wait stopped early\n"); + } + } + + if (wait() == 0) { + panic("wait got too many\n"); + } + + cprintf("forktest pass.\n"); + return 0; +} + diff --git a/code/lab7/user/forktree.c b/code/lab7/user/forktree.c new file mode 100644 index 0000000..c7df9ea --- /dev/null +++ b/code/lab7/user/forktree.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#define DEPTH 4 +#define SLEEP_TIME 400 +void forktree(const char *cur); + +void +forkchild(const char *cur, char branch) { + char nxt[DEPTH + 1]; + + if (strlen(cur) >= DEPTH) + return; + + snprintf(nxt, DEPTH + 1, "%s%c", cur, branch); + if (fork() == 0) { + forktree(nxt); + yield(); + exit(0); + } +} + +void +forktree(const char *cur) { + cprintf("%04x: I am '%s'\n", getpid(), cur); + + forkchild(cur, '0'); + forkchild(cur, '1'); +} + +int +main(void) { + cprintf("forktree process will sleep %d ticks\n",SLEEP_TIME); + sleep(SLEEP_TIME); + forktree(""); + return 0; +} + diff --git a/code/lab7/user/hello.c b/code/lab7/user/hello.c new file mode 100644 index 0000000..0f05251 --- /dev/null +++ b/code/lab7/user/hello.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("Hello world!!.\n"); + cprintf("I am process %d.\n", getpid()); + cprintf("hello pass.\n"); + return 0; +} + diff --git a/code/lab7/user/libs/initcode.S b/code/lab7/user/libs/initcode.S new file mode 100644 index 0000000..77a91d6 --- /dev/null +++ b/code/lab7/user/libs/initcode.S @@ -0,0 +1,14 @@ +.text +.globl _start +_start: + # set ebp for backtrace + movl $0x0, %ebp + + # move down the esp register + # since it may cause page fault in backtrace + subl $0x20, %esp + + # call user-program function + call umain +1: jmp 1b + diff --git a/code/lab7/user/libs/panic.c b/code/lab7/user/libs/panic.c new file mode 100644 index 0000000..783be16 --- /dev/null +++ b/code/lab7/user/libs/panic.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +void +__panic(const char *file, int line, const char *fmt, ...) { + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("user panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + exit(-E_PANIC); +} + +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("user warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + diff --git a/code/lab7/user/libs/stdio.c b/code/lab7/user/libs/stdio.c new file mode 100644 index 0000000..69b7749 --- /dev/null +++ b/code/lab7/user/libs/stdio.c @@ -0,0 +1,62 @@ +#include +#include +#include + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + sys_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + int cnt = vcprintf(fmt, ap); + va_end(ap); + + return cnt; +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + diff --git a/code/lab7/user/libs/syscall.c b/code/lab7/user/libs/syscall.c new file mode 100644 index 0000000..08517f2 --- /dev/null +++ b/code/lab7/user/libs/syscall.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +#define MAX_ARGS 5 + +static inline int +syscall(int num, ...) { + va_list ap; + va_start(ap, num); + uint32_t a[MAX_ARGS]; + int i, ret; + for (i = 0; i < MAX_ARGS; i ++) { + a[i] = va_arg(ap, uint32_t); + } + va_end(ap); + + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), + "a" (num), + "d" (a[0]), + "c" (a[1]), + "b" (a[2]), + "D" (a[3]), + "S" (a[4]) + : "cc", "memory"); + return ret; +} + +int +sys_exit(int error_code) { + return syscall(SYS_exit, error_code); +} + +int +sys_fork(void) { + return syscall(SYS_fork); +} + +int +sys_wait(int pid, int *store) { + return syscall(SYS_wait, pid, store); +} + +int +sys_yield(void) { + return syscall(SYS_yield); +} + +int +sys_kill(int pid) { + return syscall(SYS_kill, pid); +} + +int +sys_getpid(void) { + return syscall(SYS_getpid); +} + +int +sys_putc(int c) { + return syscall(SYS_putc, c); +} + +int +sys_pgdir(void) { + return syscall(SYS_pgdir); +} + +size_t +sys_gettime(void) { + return syscall(SYS_gettime); +} + +void +sys_lab6_set_priority(uint32_t priority) +{ + syscall(SYS_lab6_set_priority, priority); +} + +int +sys_sleep(unsigned int time) { + return syscall(SYS_sleep, time); +} diff --git a/code/lab7/user/libs/syscall.h b/code/lab7/user/libs/syscall.h new file mode 100644 index 0000000..c97d3d4 --- /dev/null +++ b/code/lab7/user/libs/syscall.h @@ -0,0 +1,18 @@ +#ifndef __USER_LIBS_SYSCALL_H__ +#define __USER_LIBS_SYSCALL_H__ + +int sys_exit(int error_code); +int sys_fork(void); +int sys_wait(int pid, int *store); +int sys_yield(void); +int sys_kill(int pid); +int sys_getpid(void); +int sys_putc(int c); +int sys_pgdir(void); +/* FOR LAB6 ONLY */ +void sys_lab6_set_priority(uint32_t priority); + +int sys_sleep(unsigned int time); + +#endif /* !__USER_LIBS_SYSCALL_H__ */ + diff --git a/code/lab7/user/libs/ulib.c b/code/lab7/user/libs/ulib.c new file mode 100644 index 0000000..9646710 --- /dev/null +++ b/code/lab7/user/libs/ulib.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +void +exit(int error_code) { + sys_exit(error_code); + cprintf("BUG: exit failed.\n"); + while (1); +} + +int +fork(void) { + return sys_fork(); +} + +int +wait(void) { + return sys_wait(0, NULL); +} + +int +waitpid(int pid, int *store) { + return sys_wait(pid, store); +} + +void +yield(void) { + sys_yield(); +} + +int +kill(int pid) { + return sys_kill(pid); +} + +int +getpid(void) { + return sys_getpid(); +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + sys_pgdir(); +} + +unsigned int +gettime_msec(void) { + return (unsigned int)sys_gettime(); +} + +void +lab6_set_priority(uint32_t priority) +{ + sys_lab6_set_priority(priority); +} + +int +sleep(unsigned int time) { + return sys_sleep(time); +} diff --git a/code/lab7/user/libs/ulib.h b/code/lab7/user/libs/ulib.h new file mode 100644 index 0000000..4bf6574 --- /dev/null +++ b/code/lab7/user/libs/ulib.h @@ -0,0 +1,39 @@ +#ifndef __USER_LIBS_ULIB_H__ +#define __USER_LIBS_ULIB_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +void __noreturn exit(int error_code); +int fork(void); +int wait(void); +int waitpid(int pid, int *store); +void yield(void); +int kill(int pid); +int getpid(void); +void print_pgdir(void); +unsigned int gettime_msec(void); +void lab6_set_priority(uint32_t priority); +int sleep(unsigned int time); + +#endif /* !__USER_LIBS_ULIB_H__ */ + diff --git a/code/lab7/user/libs/umain.c b/code/lab7/user/libs/umain.c new file mode 100644 index 0000000..c352072 --- /dev/null +++ b/code/lab7/user/libs/umain.c @@ -0,0 +1,10 @@ +#include + +int main(void); + +void +umain(void) { + int ret = main(); + exit(ret); +} + diff --git a/code/lab7/user/matrix.c b/code/lab7/user/matrix.c new file mode 100644 index 0000000..c5ec869 --- /dev/null +++ b/code/lab7/user/matrix.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#define MATSIZE 10 + +static int mata[MATSIZE][MATSIZE]; +static int matb[MATSIZE][MATSIZE]; +static int matc[MATSIZE][MATSIZE]; + +void +work(unsigned int times) { + int i, j, k, size = MATSIZE; + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + mata[i][j] = matb[i][j] = 1; + } + } + + yield(); + + cprintf("pid %d is running (%d times)!.\n", getpid(), times); + + while (times -- > 0) { + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + matc[i][j] = 0; + for (k = 0; k < size; k ++) { + matc[i][j] += mata[i][k] * matb[k][j]; + } + } + } + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + mata[i][j] = matb[i][j] = matc[i][j]; + } + } + } + cprintf("pid %d done!.\n", getpid()); + exit(0); +} + +const int total = 21; + +int +main(void) { + int pids[total]; + memset(pids, 0, sizeof(pids)); + + int i; + for (i = 0; i < total; i ++) { + if ((pids[i] = fork()) == 0) { + srand(i * i); + int times = (((unsigned int)rand()) % total); + times = (times * times + 10) * 100; + work(times); + } + if (pids[i] < 0) { + goto failed; + } + } + + cprintf("fork ok.\n"); + + for (i = 0; i < total; i ++) { + if (wait() != 0) { + cprintf("wait failed.\n"); + goto failed; + } + } + + cprintf("matrix pass.\n"); + return 0; + +failed: + for (i = 0; i < total; i ++) { + if (pids[i] > 0) { + kill(pids[i]); + } + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/pgdir.c b/code/lab7/user/pgdir.c new file mode 100644 index 0000000..09fd7e3 --- /dev/null +++ b/code/lab7/user/pgdir.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("I am %d, print pgdir.\n", getpid()); + print_pgdir(); + cprintf("pgdir pass.\n"); + return 0; +} + diff --git a/code/lab7/user/priority.c b/code/lab7/user/priority.c new file mode 100644 index 0000000..d71147e --- /dev/null +++ b/code/lab7/user/priority.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#define TOTAL 5 +/* to get enough accuracy, MAX_TIME (the running time of each process) should >1000 mseconds. */ +#define MAX_TIME 2000 +#define SLEEP_TIME 400 +unsigned int acc[TOTAL]; +int status[TOTAL]; +int pids[TOTAL]; + +static void +spin_delay(void) +{ + int i; + volatile int j; + for (i = 0; i != 200; ++ i) + { + j = !j; + } +} + +int +main(void) { + int i,time; + cprintf("priority process will sleep %d ticks\n",SLEEP_TIME); + sleep(SLEEP_TIME); + memset(pids, 0, sizeof(pids)); + lab6_set_priority(TOTAL + 1); + + for (i = 0; i < TOTAL; i ++) { + acc[i]=0; + if ((pids[i] = fork()) == 0) { + lab6_set_priority(i + 1); + acc[i] = 0; + while (1) { + spin_delay(); + ++ acc[i]; + if(acc[i]%4000==0) { + if((time=gettime_msec())>MAX_TIME) { + cprintf("child pid %d, acc %d, time %d\n",getpid(),acc[i],time); + exit(acc[i]); + } + } + } + + } + if (pids[i] < 0) { + goto failed; + } + } + + cprintf("main: fork ok,now need to wait pids.\n"); + + for (i = 0; i < TOTAL; i ++) { + status[i]=0; + waitpid(pids[i],&status[i]); + cprintf("main: pid %d, acc %d, time %d\n",pids[i],status[i],gettime_msec()); + } + cprintf("main: wait pids over\n"); + cprintf("stride sched correct result:"); + for (i = 0; i < TOTAL; i ++) + { + cprintf(" %d", (status[i] * 2 / status[0] + 1) / 2); + } + cprintf("\n"); + + return 0; + +failed: + for (i = 0; i < TOTAL; i ++) { + if (pids[i] > 0) { + kill(pids[i]); + } + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/sleep.c b/code/lab7/user/sleep.c new file mode 100644 index 0000000..5bc56e0 --- /dev/null +++ b/code/lab7/user/sleep.c @@ -0,0 +1,28 @@ +#include +#include + +void +sleepy(int pid) { + int i, time = 100; + for (i = 0; i < 10; i ++) { + sleep(time); + cprintf("sleep %d x %d slices.\n", i + 1, time); + } + exit(0); +} + +int +main(void) { + unsigned int time = gettime_msec(); + int pid1, exit_code; + + if ((pid1 = fork()) == 0) { + sleepy(pid1); + } + + assert(waitpid(pid1, &exit_code) == 0 && exit_code == 0); + cprintf("use %04d msecs.\n", gettime_msec() - time); + cprintf("sleep pass.\n"); + return 0; +} + diff --git a/code/lab7/user/sleepkill.c b/code/lab7/user/sleepkill.c new file mode 100644 index 0000000..01268a2 --- /dev/null +++ b/code/lab7/user/sleepkill.c @@ -0,0 +1,18 @@ +#include +#include + +int +main(void) { + int pid; + if ((pid = fork()) == 0) { + sleep(~0); + exit(0xdead); + } + assert(pid > 0); + + sleep(100); + assert(kill(pid) == 0); + cprintf("sleepkill pass.\n"); + return 0; +} + diff --git a/code/lab7/user/softint.c b/code/lab7/user/softint.c new file mode 100644 index 0000000..2f14d15 --- /dev/null +++ b/code/lab7/user/softint.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + asm volatile("int $14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/spin.c b/code/lab7/user/spin.c new file mode 100644 index 0000000..d41b919 --- /dev/null +++ b/code/lab7/user/spin.c @@ -0,0 +1,32 @@ +#include +#include + +int +main(void) { + int pid, ret, i ,j; + cprintf("I am the parent. Forking the child...\n"); + pid = fork(); + if (pid== 0) { + cprintf("I am the child. spinning ...\n"); + while (1); + }else if (pid<0) { + panic("fork child error\n"); + } + cprintf("I am the parent. Running the child...\n"); + + yield(); + yield(); + yield(); + + cprintf("I am the parent. Killing the child...\n"); + + assert((ret = kill(pid)) == 0); + cprintf("kill returns %d\n", ret); + + assert((ret = waitpid(pid, NULL)) == 0); + cprintf("wait returns %d\n", ret); + + cprintf("spin may pass.\n"); + return 0; +} + diff --git a/code/lab7/user/testbss.c b/code/lab7/user/testbss.c new file mode 100644 index 0000000..14dc6e1 --- /dev/null +++ b/code/lab7/user/testbss.c @@ -0,0 +1,33 @@ +#include +#include + +#define ARRAYSIZE (1024*1024) + +uint32_t bigarray[ARRAYSIZE]; + +int +main(void) { + cprintf("Making sure bss works right...\n"); + int i; + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != 0) { + panic("bigarray[%d] isn't cleared!\n", i); + } + } + for (i = 0; i < ARRAYSIZE; i ++) { + bigarray[i] = i; + } + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != i) { + panic("bigarray[%d] didn't hold its value!\n", i); + } + } + + cprintf("Yes, good. Now doing a wild write off the end...\n"); + cprintf("testbss may pass.\n"); + + bigarray[ARRAYSIZE + 1024] = 0; + asm volatile ("int $0x14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/waitkill.c b/code/lab7/user/waitkill.c new file mode 100644 index 0000000..9bb3f80 --- /dev/null +++ b/code/lab7/user/waitkill.c @@ -0,0 +1,59 @@ +#include +#include + +void +do_yield(void) { + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); +} + +int parent, pid1, pid2; + +void +loop(void) { + cprintf("child 1.\n"); + while (1); +} + +void +work(void) { + cprintf("child 2.\n"); + do_yield(); + if (kill(parent) == 0) { + cprintf("kill parent ok.\n"); + do_yield(); + if (kill(pid1) == 0) { + cprintf("kill child1 ok.\n"); + exit(0); + } + } + exit(-1); +} + +int +main(void) { + parent = getpid(); + if ((pid1 = fork()) == 0) { + loop(); + } + + assert(pid1 > 0); + + if ((pid2 = fork()) == 0) { + work(); + } + if (pid2 > 0) { + cprintf("wait child 1.\n"); + waitpid(pid1, NULL); + panic("waitpid %d returns\n", pid1); + } + else { + kill(pid1); + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab7/user/yield.c b/code/lab7/user/yield.c new file mode 100644 index 0000000..a19890d --- /dev/null +++ b/code/lab7/user/yield.c @@ -0,0 +1,16 @@ +#include +#include + +int +main(void) { + int i; + cprintf("Hello, I am process %d.\n", getpid()); + for (i = 0; i < 5; i ++) { + yield(); + cprintf("Back in process %d, iteration %d.\n", getpid(), i); + } + cprintf("All done in process %d.\n", getpid()); + cprintf("yield pass.\n"); + return 0; +} + diff --git a/code/lab8/Makefile b/code/lab8/Makefile new file mode 100644 index 0000000..884b4cc --- /dev/null +++ b/code/lab8/Makefile @@ -0,0 +1,369 @@ +PROJ := 5 +EMPTY := +SPACE := $(EMPTY) $(EMPTY) +SLASH := / + +V := @ + +# try to infer the correct GCCPREFX +ifndef GCCPREFIX +GCCPREFIX := $(shell if i386-ucore-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \ + then echo 'i386-ucore-elf-'; \ + elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \ + then echo ''; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find an i386-ucore-elf version of GCC/binutils." 1>&2; \ + echo "*** Is the directory with i386-ucore-elf-gcc in your PATH?" 1>&2; \ + echo "*** If your i386-ucore-elf toolchain is installed with a command" 1>&2; \ + echo "*** prefix other than 'i386-ucore-elf-', set your GCCPREFIX" 1>&2; \ + echo "*** environment variable to that prefix and run 'make' again." 1>&2; \ + echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# try to infer the correct QEMU +ifndef QEMU +QEMU := $(shell if which qemu > /dev/null; \ + then echo 'qemu'; exit; \ + elif which i386-ucore-elf-qemu > /dev/null; \ + then echo 'i386-ucore-elf-qemu'; exit; \ + else \ + echo "***" 1>&2; \ + echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \ + echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +# eliminate default suffix rules +.SUFFIXES: .c .S .h + +# delete target files if there is an error (or make is interrupted) +.DELETE_ON_ERROR: + +# define compiler and flags + +HOSTCC := gcc +HOSTCFLAGS := -g -Wall -O2 -D_FILE_OFFSET_BITS=64 + +GDB := $(GCCPREFIX)gdb + +CC ?= $(GCCPREFIX)gcc +CFLAGS := -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc $(DEFS) +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +CTYPE := c S + +LD := $(GCCPREFIX)ld +LDFLAGS := -m $(shell $(LD) -V | grep elf_i386 2>/dev/null) +LDFLAGS += -nostdlib + +OBJCOPY := $(GCCPREFIX)objcopy +OBJDUMP := $(GCCPREFIX)objdump + +COPY := cp +MKDIR := mkdir -p +MV := mv +RM := rm -f +AWK := awk +SED := sed +SH := sh +TR := tr +TOUCH := touch -c + +OBJDIR := obj +BINDIR := bin + +ALLOBJS := +ALLDEPS := +TARGETS := + +USER_PREFIX := __user_ + +include tools/function.mk + +listf_cc = $(call listf,$(1),$(CTYPE)) + +# for cc +add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4)) +create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS)) + +# for hostcc +add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3)) +create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS)) + +cgtype = $(patsubst %.$(2),%.$(3),$(1)) +objfile = $(call toobj,$(1)) +asmfile = $(call cgtype,$(call toobj,$(1)),o,asm) +outfile = $(call cgtype,$(call toobj,$(1)),o,out) +symfile = $(call cgtype,$(call toobj,$(1)),o,sym) +filename = $(basename $(notdir $(1))) +ubinfile = $(call outfile,$(addprefix $(USER_PREFIX),$(call filename,$(1)))) + +# for match pattern +match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?) + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +# include kernel/user + +INCLUDE += libs/ + +CFLAGS += $(addprefix -I,$(INCLUDE)) + +LIBDIR += libs + +$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,) + +# ------------------------------------------------------------------- +# user programs + +UINCLUDE += user/include/ \ + user/libs/ + +USRCDIR += user + +ULIBDIR += user/libs + +UCFLAGS += $(addprefix -I,$(UINCLUDE)) +USER_BINS := + +$(call add_files_cc,$(call listf_cc,$(ULIBDIR)),ulibs,$(UCFLAGS)) +$(call add_files_cc,$(call listf_cc,$(USRCDIR)),uprog,$(UCFLAGS)) + +UOBJS := $(call read_packet,ulibs libs) + +define uprog_ld +__user_bin__ := $$(call ubinfile,$(1)) +USER_BINS += $$(__user_bin__) +$$(__user_bin__): tools/user.ld +$$(__user_bin__): $$(UOBJS) +$$(__user_bin__): $(1) | $$$$(dir $$$$@) + $(V)$(LD) $(LDFLAGS) -T tools/user.ld -o $$@ $$(UOBJS) $(1) + @$(OBJDUMP) -S $$@ > $$(call cgtype,$$<,o,asm) + @$(OBJDUMP) -t $$@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$$$/d' > $$(call cgtype,$$<,o,sym) +endef + +$(foreach p,$(call read_packet,uprog),$(eval $(call uprog_ld,$(p)))) + +# ------------------------------------------------------------------- +# kernel + +KINCLUDE += kern/debug/ \ + kern/driver/ \ + kern/trap/ \ + kern/mm/ \ + kern/libs/ \ + kern/sync/ \ + kern/fs/ \ + kern/process/ \ + kern/schedule/ \ + kern/syscall/ \ + kern/fs/swap/ \ + kern/fs/vfs/ \ + kern/fs/devs/ \ + kern/fs/sfs/ + + +KSRCDIR += kern/init \ + kern/libs \ + kern/debug \ + kern/driver \ + kern/trap \ + kern/mm \ + kern/sync \ + kern/fs \ + kern/process \ + kern/schedule \ + kern/syscall \ + kern/fs/swap \ + kern/fs/vfs \ + kern/fs/devs \ + kern/fs/sfs + +KCFLAGS += $(addprefix -I,$(KINCLUDE)) + +$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS)) + +KOBJS = $(call read_packet,kernel libs) + +# create kernel target +kernel = $(call totarget,kernel) + +$(kernel): tools/kernel.ld + +$(kernel): $(KOBJS) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS) + @$(OBJDUMP) -S $@ > $(call asmfile,kernel) + @$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel) + +$(call create_target,kernel) + +# ------------------------------------------------------------------- + +# create bootblock +bootfiles = $(call listf_cc,boot) +$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc)) + +bootblock = $(call totarget,bootblock) + +$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign) + @echo + ld $@ + $(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock) + @$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock) + @$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock) + @$(call totarget,sign) $(call outfile,bootblock) $(bootblock) + +$(call create_target,bootblock) + +# ------------------------------------------------------------------- + +# create 'sign' tools +$(call add_files_host,tools/sign.c,sign,sign) +$(call create_target_host,sign,sign) + +# ------------------------------------------------------------------- +# create 'mksfs' tools +$(call add_files_host,tools/mksfs.c,mksfs,mksfs) +$(call create_target_host,mksfs,mksfs) + +# ------------------------------------------------------------------- +# create ucore.img +UCOREIMG := $(call totarget,ucore.img) + +$(UCOREIMG): $(kernel) $(bootblock) + $(V)dd if=/dev/zero of=$@ count=10000 + $(V)dd if=$(bootblock) of=$@ conv=notrunc + $(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc + +$(call create_target,ucore.img) + +# ------------------------------------------------------------------- + +# create swap.img +SWAPIMG := $(call totarget,swap.img) + +$(SWAPIMG): + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + +$(call create_target,swap.img) + +# ------------------------------------------------------------------- +# create sfs.img +SFSIMG := $(call totarget,sfs.img) +SFSBINS := +SFSROOT := disk0 + +define fscopy +__fs_bin__ := $(2)$(SLASH)$(patsubst $(USER_PREFIX)%,%,$(basename $(notdir $(1)))) +SFSBINS += $$(__fs_bin__) +$$(__fs_bin__): $(1) | $$$$(dir $@) + @$(COPY) $$< $$@ +endef + +$(foreach p,$(USER_BINS),$(eval $(call fscopy,$(p),$(SFSROOT)$(SLASH)))) + +$(SFSIMG): $(SFSROOT) $(SFSBINS) | $(call totarget,mksfs) + $(V)dd if=/dev/zero of=$@ bs=1M count=128 + @$(call totarget,mksfs) $@ $(SFSROOT) + +$(call create_target,sfs.img) + + +# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +$(call finish_all) + +IGNORE_ALLDEPS = clean \ + dist-clean \ + grade \ + touch \ + print-.+ \ + run-.+ \ + build-.+ \ + sh-.+ \ + script-.+ \ + handin + +ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0) +-include $(ALLDEPS) +endif + +# files for grade script + +TARGETS: $(TARGETS) + +.DEFAULT_GOAL := TARGETS + +QEMUOPTS = -hda $(UCOREIMG) -drive file=$(SWAPIMG),media=disk,cache=writeback -drive file=$(SFSIMG),media=disk,cache=writeback + +.PHONY: qemu qemu-nox debug debug-nox monitor +qemu: $(UCOREIMG) $(SWAPIMG) $(SFSIMG) + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +qemu-nox: $(UCOREIMG) $(SWAPIMG) $(SFSIMG) + $(V)$(QEMU) -serial mon:stdio $(QEMUOPTS) -nographic + +monitor: $(UCOREIMG) $(SWAPING) $(SFSIMG) + $(V)$(QEMU) -monitor stdio $(QEMUOPTS) -serial null + +TERMINAL := gnome-terminal + +debug: $(UCOREIMG) $(SWAPIMG) $(SFSIMG) + $(V)$(QEMU) -S -s -parallel stdio $(QEMUOPTS) -serial null & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +debug-nox: $(UCOREIMG) $(SWAPIMG) $(SFSIMG) + $(V)$(QEMU) -S -s -serial mon:stdio $(QEMUOPTS) -nographic & + $(V)sleep 2 + $(V)$(TERMINAL) -e "$(GDB) -q -x tools/gdbinit" + +RUN_PREFIX := _binary_$(OBJDIR)_$(USER_PREFIX) +MAKEOPTS := --quiet --no-print-directory + +run-%: build-% + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +sh-%: script-% + $(V)$(QEMU) -parallel stdio $(QEMUOPTS) -serial null + +build-%: touch + $(V)$(MAKE) $(MAKEOPTS) "DEFS+=-DTEST=$*" + +script-%: touch + $(V)$(MAKE) $(MAKEOPTS) "DEFS+=-DTEST=sh -DTESTSCRIPT=/script/$*" + +.PHONY: grade touch buildfs + +GRADE_GDB_IN := .gdb.in +GRADE_QEMU_OUT := .qemu.out +HANDIN := proj$(PROJ)-handin.tar.gz + +TOUCH_FILES := kern/process/proc.c + +MAKEOPTS := --quiet --no-print-directory + +grade: + $(V)$(MAKE) $(MAKEOPTS) clean + $(V)$(SH) tools/grade.sh + +touch: + $(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f)) + +print-%: + @echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z])) + +.PHONY: clean dist-clean handin packall +clean: + $(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) $(SFSBINS) + -$(RM) -r $(OBJDIR) $(BINDIR) + +dist-clean: clean + -$(RM) $(HANDIN) + +handin: packall + @echo Please visit http://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks! + +packall: clean + @$(RM) -f $(HANDIN) + @tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'` + diff --git a/code/lab8/boot/asm.h b/code/lab8/boot/asm.h new file mode 100644 index 0000000..8e0405a --- /dev/null +++ b/code/lab8/boot/asm.h @@ -0,0 +1,26 @@ +#ifndef __BOOT_ASM_H__ +#define __BOOT_ASM_H__ + +/* Assembler macros to create x86 segments */ + +/* Normal segment */ +#define SEG_NULLASM \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +#endif /* !__BOOT_ASM_H__ */ + diff --git a/code/lab8/boot/bootasm.S b/code/lab8/boot/bootasm.S new file mode 100644 index 0000000..f1852c3 --- /dev/null +++ b/code/lab8/boot/bootasm.S @@ -0,0 +1,107 @@ +#include + +# Start the CPU: switch to 32-bit protected mode, jump into C. +# The BIOS loads this code from the first sector of the hard disk into +# memory at physical address 0x7c00 and starts executing in real mode +# with %cs=0 %ip=7c00. + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector +.set CR0_PE_ON, 0x1 # protected mode enable flag +.set SMAP, 0x534d4150 + +# start address should be 0:7c00, in real mode, the beginning address of the running bootloader +.globl start +start: +.code16 # Assemble for 16-bit mode + cli # Disable interrupts + cld # String operations increment + + # Set up the important data segment registers (DS, ES, SS). + xorw %ax, %ax # Segment number zero + movw %ax, %ds # -> Data Segment + movw %ax, %es # -> Extra Segment + movw %ax, %ss # -> Stack Segment + + # Enable A20: + # For backwards compatibility with the earliest PCs, physical + # address line 20 is tied low, so that addresses higher than + # 1MB wrap around to zero by default. This code undoes this. +seta20.1: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.1 + + movb $0xd1, %al # 0xd1 -> port 0x64 + outb %al, $0x64 + +seta20.2: + inb $0x64, %al # Wait for not busy + testb $0x2, %al + jnz seta20.2 + + movb $0xdf, %al # 0xdf -> port 0x60 + outb %al, $0x60 + +probe_memory: + movl $0, 0x8000 + xorl %ebx, %ebx + movw $0x8004, %di +start_probe: + movl $0xE820, %eax + movl $20, %ecx + movl $SMAP, %edx + int $0x15 + jnc cont + movw $12345, 0x8000 + jmp finish_probe +cont: + addw $20, %di + incl 0x8000 + cmpl $0, %ebx + jnz start_probe +finish_probe: + + # Switch from real to protected mode, using a bootstrap GDT + # and segment translation that makes virtual addresses + # identical to physical addresses, so that the + # effective memory map does not change during the switch. + lgdt gdtdesc + movl %cr0, %eax + orl $CR0_PE_ON, %eax + movl %eax, %cr0 + + # Jump to next instruction, but in 32-bit code segment. + # Switches processor into 32-bit mode. + ljmp $PROT_MODE_CSEG, $protcseg + +.code32 # Assemble for 32-bit mode +protcseg: + # Set up the protected-mode data segment registers + movw $PROT_MODE_DSEG, %ax # Our data segment selector + movw %ax, %ds # -> DS: Data Segment + movw %ax, %es # -> ES: Extra Segment + movw %ax, %fs # -> FS + movw %ax, %gs # -> GS + movw %ax, %ss # -> SS: Stack Segment + + # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) + movl $0x0, %ebp + movl $start, %esp + call bootmain + + # If bootmain returns (it shouldn't), loop. +spin: + jmp spin + +.data +# Bootstrap GDT +.p2align 2 # force 4 byte alignment +gdt: + SEG_NULLASM # null seg + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel + +gdtdesc: + .word 0x17 # sizeof(gdt) - 1 + .long gdt # address gdt diff --git a/code/lab8/boot/bootmain.c b/code/lab8/boot/bootmain.c new file mode 100644 index 0000000..4b55eb7 --- /dev/null +++ b/code/lab8/boot/bootmain.c @@ -0,0 +1,116 @@ +#include +#include +#include + +/* ********************************************************************* + * This a dirt simple boot loader, whose sole job is to boot + * an ELF kernel image from the first IDE hard disk. + * + * DISK LAYOUT + * * This program(bootasm.S and bootmain.c) is the bootloader. + * It should be stored in the first sector of the disk. + * + * * The 2nd sector onward holds the kernel image. + * + * * The kernel image must be in ELF format. + * + * BOOT UP STEPS + * * when the CPU boots it loads the BIOS into memory and executes it + * + * * the BIOS intializes devices, sets of the interrupt routines, and + * reads the first sector of the boot device(e.g., hard-drive) + * into memory and jumps to it. + * + * * Assuming this boot loader is stored in the first sector of the + * hard-drive, this code takes over... + * + * * control starts in bootasm.S -- which sets up protected mode, + * and a stack so C code then run, then calls bootmain() + * + * * bootmain() in this file takes over, reads in the kernel and jumps to it. + * */ + +#define SECTSIZE 512 +#define ELFHDR ((struct elfhdr *)0x10000) // scratch space + +/* waitdisk - wait for disk ready */ +static void +waitdisk(void) { + while ((inb(0x1F7) & 0xC0) != 0x40) + /* do nothing */; +} + +/* readsect - read a single sector at @secno into @dst */ +static void +readsect(void *dst, uint32_t secno) { + // wait for disk to be ready + waitdisk(); + + outb(0x1F2, 1); // count = 1 + outb(0x1F3, secno & 0xFF); + outb(0x1F4, (secno >> 8) & 0xFF); + outb(0x1F5, (secno >> 16) & 0xFF); + outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); + outb(0x1F7, 0x20); // cmd 0x20 - read sectors + + // wait for disk to be ready + waitdisk(); + + // read a sector + insl(0x1F0, dst, SECTSIZE / 4); +} + +/* * + * readseg - read @count bytes at @offset from kernel into virtual address @va, + * might copy more than asked. + * */ +static void +readseg(uintptr_t va, uint32_t count, uint32_t offset) { + uintptr_t end_va = va + count; + + // round down to sector boundary + va -= offset % SECTSIZE; + + // translate from bytes to sectors; kernel starts at sector 1 + uint32_t secno = (offset / SECTSIZE) + 1; + + // If this is too slow, we could read lots of sectors at a time. + // We'd write more to memory than asked, but it doesn't matter -- + // we load in increasing order. + for (; va < end_va; va += SECTSIZE, secno ++) { + readsect((void *)va, secno); + } +} + +/* bootmain - the entry of bootloader */ +void +bootmain(void) { + // read the 1st page off disk + readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); + + // is this a valid ELF? + if (ELFHDR->e_magic != ELF_MAGIC) { + goto bad; + } + + struct proghdr *ph, *eph; + + // load each program segment (ignores ph flags) + ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); + eph = ph + ELFHDR->e_phnum; + for (; ph < eph; ph ++) { + readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); + } + + // call the entry point from the ELF header + // note: does not return + ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); + +bad: + outw(0x8A00, 0x8A00); + outw(0x8A00, 0x8E00); + + /* do nothing */ + while (1); +} + diff --git a/code/lab8/kern/debug/assert.h b/code/lab8/kern/debug/assert.h new file mode 100644 index 0000000..ac1a966 --- /dev/null +++ b/code/lab8/kern/debug/assert.h @@ -0,0 +1,27 @@ +#ifndef __KERN_DEBUG_ASSERT_H__ +#define __KERN_DEBUG_ASSERT_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +#endif /* !__KERN_DEBUG_ASSERT_H__ */ + diff --git a/code/lab8/kern/debug/kdebug.c b/code/lab8/kern/debug/kdebug.c new file mode 100644 index 0000000..fedbf5b --- /dev/null +++ b/code/lab8/kern/debug/kdebug.c @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACKFRAME_DEPTH 20 + +extern const struct stab __STAB_BEGIN__[]; // beginning of stabs table +extern const struct stab __STAB_END__[]; // end of stabs table +extern const char __STABSTR_BEGIN__[]; // beginning of string table +extern const char __STABSTR_END__[]; // end of string table + +/* debug information about a particular instruction pointer */ +struct eipdebuginfo { + const char *eip_file; // source code filename for eip + int eip_line; // source code line number for eip + const char *eip_fn_name; // name of function containing eip + int eip_fn_namelen; // length of function's name + uintptr_t eip_fn_addr; // start address of function + int eip_fn_narg; // number of function arguments +}; + +/* user STABS data structure */ +struct userstabdata { + const struct stab *stabs; + const struct stab *stab_end; + const char *stabstr; + const char *stabstr_end; +}; + +/* * + * stab_binsearch - according to the input, the initial value of + * range [*@region_left, *@region_right], find a single stab entry + * that includes the address @addr and matches the type @type, + * and then save its boundary to the locations that pointed + * by @region_left and @region_right. + * + * Some stab types are arranged in increasing order by instruction address. + * For example, N_FUN stabs (stab entries with n_type == N_FUN), which + * mark functions, and N_SO stabs, which mark source files. + * + * Given an instruction address, this function finds the single stab entry + * of type @type that contains that address. + * + * The search takes place within the range [*@region_left, *@region_right]. + * Thus, to search an entire set of N stabs, you might do: + * + * left = 0; + * right = N - 1; (rightmost stab) + * stab_binsearch(stabs, &left, &right, type, addr); + * + * The search modifies *region_left and *region_right to bracket the @addr. + * *@region_left points to the matching stab that contains @addr, + * and *@region_right points just before the next stab. + * If *@region_left > *region_right, then @addr is not contained in any + * matching stab. + * + * For example, given these N_SO stabs: + * Index Type Address + * 0 SO f0100000 + * 13 SO f0100040 + * 117 SO f0100176 + * 118 SO f0100178 + * 555 SO f0100652 + * 556 SO f0100654 + * 657 SO f0100849 + * this code: + * left = 0, right = 657; + * stab_binsearch(stabs, &left, &right, N_SO, 0xf0100184); + * will exit setting left = 118, right = 554. + * */ +static void +stab_binsearch(const struct stab *stabs, int *region_left, int *region_right, + int type, uintptr_t addr) { + int l = *region_left, r = *region_right, any_matches = 0; + + while (l <= r) { + int true_m = (l + r) / 2, m = true_m; + + // search for earliest stab with right type + while (m >= l && stabs[m].n_type != type) { + m --; + } + if (m < l) { // no match in [l, m] + l = true_m + 1; + continue; + } + + // actual binary search + any_matches = 1; + if (stabs[m].n_value < addr) { + *region_left = m; + l = true_m + 1; + } else if (stabs[m].n_value > addr) { + *region_right = m - 1; + r = m - 1; + } else { + // exact match for 'addr', but continue loop to find + // *region_right + *region_left = m; + l = m; + addr ++; + } + } + + if (!any_matches) { + *region_right = *region_left - 1; + } + else { + // find rightmost region containing 'addr' + l = *region_right; + for (; l > *region_left && stabs[l].n_type != type; l --) + /* do nothing */; + *region_left = l; + } +} + +/* * + * debuginfo_eip - Fill in the @info structure with information about + * the specified instruction address, @addr. Returns 0 if information + * was found, and negative if not. But even if it returns negative it + * has stored some information into '*info'. + * */ +int +debuginfo_eip(uintptr_t addr, struct eipdebuginfo *info) { + const struct stab *stabs, *stab_end; + const char *stabstr, *stabstr_end; + + info->eip_file = ""; + info->eip_line = 0; + info->eip_fn_name = ""; + info->eip_fn_namelen = 9; + info->eip_fn_addr = addr; + info->eip_fn_narg = 0; + + // find the relevant set of stabs + if (addr >= KERNBASE) { + stabs = __STAB_BEGIN__; + stab_end = __STAB_END__; + stabstr = __STABSTR_BEGIN__; + stabstr_end = __STABSTR_END__; + } + else { + // user-program linker script, tools/user.ld puts the information about the + // program's stabs (included __STAB_BEGIN__, __STAB_END__, __STABSTR_BEGIN__, + // and __STABSTR_END__) in a structure located at virtual address USTAB. + const struct userstabdata *usd = (struct userstabdata *)USTAB; + + // make sure that debugger (current process) can access this memory + struct mm_struct *mm; + if (current == NULL || (mm = current->mm) == NULL) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)usd, sizeof(struct userstabdata), 0)) { + return -1; + } + + stabs = usd->stabs; + stab_end = usd->stab_end; + stabstr = usd->stabstr; + stabstr_end = usd->stabstr_end; + + // make sure the STABS and string table memory is valid + if (!user_mem_check(mm, (uintptr_t)stabs, (uintptr_t)stab_end - (uintptr_t)stabs, 0)) { + return -1; + } + if (!user_mem_check(mm, (uintptr_t)stabstr, stabstr_end - stabstr, 0)) { + return -1; + } + } + + // String table validity checks + if (stabstr_end <= stabstr || stabstr_end[-1] != 0) { + return -1; + } + + // Now we find the right stabs that define the function containing + // 'eip'. First, we find the basic source file containing 'eip'. + // Then, we look in that source file for the function. Then we look + // for the line number. + + // Search the entire set of stabs for the source file (type N_SO). + int lfile = 0, rfile = (stab_end - stabs) - 1; + stab_binsearch(stabs, &lfile, &rfile, N_SO, addr); + if (lfile == 0) + return -1; + + // Search within that file's stabs for the function definition + // (N_FUN). + int lfun = lfile, rfun = rfile; + int lline, rline; + stab_binsearch(stabs, &lfun, &rfun, N_FUN, addr); + + if (lfun <= rfun) { + // stabs[lfun] points to the function name + // in the string table, but check bounds just in case. + if (stabs[lfun].n_strx < stabstr_end - stabstr) { + info->eip_fn_name = stabstr + stabs[lfun].n_strx; + } + info->eip_fn_addr = stabs[lfun].n_value; + addr -= info->eip_fn_addr; + // Search within the function definition for the line number. + lline = lfun; + rline = rfun; + } else { + // Couldn't find function stab! Maybe we're in an assembly + // file. Search the whole file for the line number. + info->eip_fn_addr = addr; + lline = lfile; + rline = rfile; + } + info->eip_fn_namelen = strfind(info->eip_fn_name, ':') - info->eip_fn_name; + + // Search within [lline, rline] for the line number stab. + // If found, set info->eip_line to the right line number. + // If not found, return -1. + stab_binsearch(stabs, &lline, &rline, N_SLINE, addr); + if (lline <= rline) { + info->eip_line = stabs[rline].n_desc; + } else { + return -1; + } + + // Search backwards from the line number for the relevant filename stab. + // We can't just use the "lfile" stab because inlined functions + // can interpolate code from a different file! + // Such included source files use the N_SOL stab type. + while (lline >= lfile + && stabs[lline].n_type != N_SOL + && (stabs[lline].n_type != N_SO || !stabs[lline].n_value)) { + lline --; + } + if (lline >= lfile && stabs[lline].n_strx < stabstr_end - stabstr) { + info->eip_file = stabstr + stabs[lline].n_strx; + } + + // Set eip_fn_narg to the number of arguments taken by the function, + // or 0 if there was no containing function. + if (lfun < rfun) { + for (lline = lfun + 1; + lline < rfun && stabs[lline].n_type == N_PSYM; + lline ++) { + info->eip_fn_narg ++; + } + } + return 0; +} + +/* * + * print_kerninfo - print the information about kernel, including the location + * of kernel entry, the start addresses of data and text segements, the start + * address of free memory and how many memory that kernel has used. + * */ +void +print_kerninfo(void) { + extern char etext[], edata[], end[], kern_init[]; + cprintf("Special kernel symbols:\n"); + cprintf(" entry 0x%08x (phys)\n", kern_init); + cprintf(" etext 0x%08x (phys)\n", etext); + cprintf(" edata 0x%08x (phys)\n", edata); + cprintf(" end 0x%08x (phys)\n", end); + cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); +} + +/* * + * print_debuginfo - read and print the stat information for the address @eip, + * and info.eip_fn_addr should be the first address of the related function. + * */ +void +print_debuginfo(uintptr_t eip) { + struct eipdebuginfo info; + if (debuginfo_eip(eip, &info) != 0) { + cprintf(" : -- 0x%08x --\n", eip); + } + else { + char fnname[256]; + int j; + for (j = 0; j < info.eip_fn_namelen; j ++) { + fnname[j] = info.eip_fn_name[j]; + } + fnname[j] = '\0'; + cprintf(" %s:%d: %s+%d\n", info.eip_file, info.eip_line, + fnname, eip - info.eip_fn_addr); + } +} + +static __noinline uint32_t +read_eip(void) { + uint32_t eip; + asm volatile("movl 4(%%ebp), %0" : "=r" (eip)); + return eip; +} + +/* * + * print_stackframe - print a list of the saved eip values from the nested 'call' + * instructions that led to the current point of execution + * + * The x86 stack pointer, namely esp, points to the lowest location on the stack + * that is currently in use. Everything below that location in stack is free. Pushing + * a value onto the stack will invole decreasing the stack pointer and then writing + * the value to the place that stack pointer pointes to. And popping a value do the + * opposite. + * + * The ebp (base pointer) register, in contrast, is associated with the stack + * primarily by software convention. On entry to a C function, the function's + * prologue code normally saves the previous function's base pointer by pushing + * it onto the stack, and then copies the current esp value into ebp for the duration + * of the function. If all the functions in a program obey this convention, + * then at any given point during the program's execution, it is possible to trace + * back through the stack by following the chain of saved ebp pointers and determining + * exactly what nested sequence of function calls caused this particular point in the + * program to be reached. This capability can be particularly useful, for example, + * when a particular function causes an assert failure or panic because bad arguments + * were passed to it, but you aren't sure who passed the bad arguments. A stack + * backtrace lets you find the offending function. + * + * The inline function read_ebp() can tell us the value of current ebp. And the + * non-inline function read_eip() is useful, it can read the value of current eip, + * since while calling this function, read_eip() can read the caller's eip from + * stack easily. + * + * In print_debuginfo(), the function debuginfo_eip() can get enough information about + * calling-chain. Finally print_stackframe() will trace and print them for debugging. + * + * Note that, the length of ebp-chain is limited. In boot/bootasm.S, before jumping + * to the kernel entry, the value of ebp has been set to zero, that's the boundary. + * */ +void +print_stackframe(void) { + /* LAB1 YOUR CODE : STEP 1 */ + /* (1) call read_ebp() to get the value of ebp. the type is (uint32_t); + * (2) call read_eip() to get the value of eip. the type is (uint32_t); + * (3) from 0 .. STACKFRAME_DEPTH + * (3.1) printf value of ebp, eip + * (3.2) (uint32_t)calling arguments [0..4] = the contents in address (unit32_t)ebp +2 [0..4] + * (3.3) cprintf("\n"); + * (3.4) call print_debuginfo(eip-1) to print the C calling function name and line number, etc. + * (3.5) popup a calling stackframe + * NOTICE: the calling funciton's return addr eip = ss:[ebp+4] + * the calling funciton's ebp = ss:[ebp] + */ +} + diff --git a/code/lab8/kern/debug/kdebug.h b/code/lab8/kern/debug/kdebug.h new file mode 100644 index 0000000..c2a7b74 --- /dev/null +++ b/code/lab8/kern/debug/kdebug.h @@ -0,0 +1,12 @@ +#ifndef __KERN_DEBUG_KDEBUG_H__ +#define __KERN_DEBUG_KDEBUG_H__ + +#include +#include + +void print_kerninfo(void); +void print_stackframe(void); +void print_debuginfo(uintptr_t eip); + +#endif /* !__KERN_DEBUG_KDEBUG_H__ */ + diff --git a/code/lab8/kern/debug/monitor.c b/code/lab8/kern/debug/monitor.c new file mode 100644 index 0000000..85ac06c --- /dev/null +++ b/code/lab8/kern/debug/monitor.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Simple command-line kernel monitor useful for controlling the + * kernel and exploring the system interactively. + * */ + +struct command { + const char *name; + const char *desc; + // return -1 to force monitor to exit + int(*func)(int argc, char **argv, struct trapframe *tf); +}; + +static struct command commands[] = { + {"help", "Display this list of commands.", mon_help}, + {"kerninfo", "Display information about the kernel.", mon_kerninfo}, + {"backtrace", "Print backtrace of stack frame.", mon_backtrace}, +}; + +/* return if kernel is panic, in kern/debug/panic.c */ +bool is_kernel_panic(void); + +#define NCOMMANDS (sizeof(commands)/sizeof(struct command)) + +/***** Kernel monitor command interpreter *****/ + +#define MAXARGS 16 +#define WHITESPACE " \t\n\r" + +/* parse - parse the command buffer into whitespace-separated arguments */ +static int +parse(char *buf, char **argv) { + int argc = 0; + while (1) { + // find global whitespace + while (*buf != '\0' && strchr(WHITESPACE, *buf) != NULL) { + *buf ++ = '\0'; + } + if (*buf == '\0') { + break; + } + + // save and scan past next arg + if (argc == MAXARGS - 1) { + cprintf("Too many arguments (max %d).\n", MAXARGS); + } + argv[argc ++] = buf; + while (*buf != '\0' && strchr(WHITESPACE, *buf) == NULL) { + buf ++; + } + } + return argc; +} + +/* * + * runcmd - parse the input string, split it into separated arguments + * and then lookup and invoke some related commands/ + * */ +static int +runcmd(char *buf, struct trapframe *tf) { + char *argv[MAXARGS]; + int argc = parse(buf, argv); + if (argc == 0) { + return 0; + } + int i; + for (i = 0; i < NCOMMANDS; i ++) { + if (strcmp(commands[i].name, argv[0]) == 0) { + return commands[i].func(argc - 1, argv + 1, tf); + } + } + cprintf("Unknown command '%s'\n", argv[0]); + return 0; +} + +/***** Implementations of basic kernel monitor commands *****/ + +void +monitor(struct trapframe *tf) { + cprintf("Welcome to the kernel debug monitor!!\n"); + cprintf("Type 'help' for a list of commands.\n"); + + if (tf != NULL) { + print_trapframe(tf); + } + + char *buf; + while (1) { + if ((buf = readline("K> ")) != NULL) { + if (runcmd(buf, tf) < 0) { + break; + } + } + } +} + +/* mon_help - print the information about mon_* functions */ +int +mon_help(int argc, char **argv, struct trapframe *tf) { + int i; + for (i = 0; i < NCOMMANDS; i ++) { + cprintf("%s - %s\n", commands[i].name, commands[i].desc); + } + return 0; +} + +/* * + * mon_kerninfo - call print_kerninfo in kern/debug/kdebug.c to + * print the memory occupancy in kernel. + * */ +int +mon_kerninfo(int argc, char **argv, struct trapframe *tf) { + print_kerninfo(); + return 0; +} + +/* * + * mon_backtrace - call print_stackframe in kern/debug/kdebug.c to + * print a backtrace of the stack. + * */ +int +mon_backtrace(int argc, char **argv, struct trapframe *tf) { + print_stackframe(); + return 0; +} + diff --git a/code/lab8/kern/debug/monitor.h b/code/lab8/kern/debug/monitor.h new file mode 100644 index 0000000..2bc0854 --- /dev/null +++ b/code/lab8/kern/debug/monitor.h @@ -0,0 +1,19 @@ +#ifndef __KERN_DEBUG_MONITOR_H__ +#define __KERN_DEBUG_MONITOR_H__ + +#include + +void monitor(struct trapframe *tf); + +int mon_help(int argc, char **argv, struct trapframe *tf); +int mon_kerninfo(int argc, char **argv, struct trapframe *tf); +int mon_backtrace(int argc, char **argv, struct trapframe *tf); +int mon_continue(int argc, char **argv, struct trapframe *tf); +int mon_step(int argc, char **argv, struct trapframe *tf); +int mon_breakpoint(int argc, char **argv, struct trapframe *tf); +int mon_watchpoint(int argc, char **argv, struct trapframe *tf); +int mon_delete_dr(int argc, char **argv, struct trapframe *tf); +int mon_list_dr(int argc, char **argv, struct trapframe *tf); + +#endif /* !__KERN_DEBUG_MONITOR_H__ */ + diff --git a/code/lab8/kern/debug/panic.c b/code/lab8/kern/debug/panic.c new file mode 100644 index 0000000..9be6c0b --- /dev/null +++ b/code/lab8/kern/debug/panic.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +static bool is_panic = 0; + +/* * + * __panic - __panic is called on unresolvable fatal errors. it prints + * "panic: 'message'", and then enters the kernel monitor. + * */ +void +__panic(const char *file, int line, const char *fmt, ...) { + if (is_panic) { + goto panic_dead; + } + is_panic = 1; + + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("kernel panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + +panic_dead: + intr_disable(); + while (1) { + monitor(NULL); + } +} + +/* __warn - like panic, but don't */ +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("kernel warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + +bool +is_kernel_panic(void) { + return is_panic; +} + diff --git a/code/lab8/kern/debug/stab.h b/code/lab8/kern/debug/stab.h new file mode 100644 index 0000000..8d5cea3 --- /dev/null +++ b/code/lab8/kern/debug/stab.h @@ -0,0 +1,54 @@ +#ifndef __KERN_DEBUG_STAB_H__ +#define __KERN_DEBUG_STAB_H__ + +#include + +/* * + * STABS debugging info + * + * The kernel debugger can understand some debugging information in + * the STABS format. For more information on this format, see + * http://sources.redhat.com/gdb/onlinedocs/stabs_toc.html + * + * The constants below define some symbol types used by various debuggers + * and compilers. Kernel uses the N_SO, N_SOL, N_FUN, and N_SLINE types. + * */ + +#define N_GSYM 0x20 // global symbol +#define N_FNAME 0x22 // F77 function name +#define N_FUN 0x24 // procedure name +#define N_STSYM 0x26 // data segment variable +#define N_LCSYM 0x28 // bss segment variable +#define N_MAIN 0x2a // main function name +#define N_PC 0x30 // global Pascal symbol +#define N_RSYM 0x40 // register variable +#define N_SLINE 0x44 // text segment line number +#define N_DSLINE 0x46 // data segment line number +#define N_BSLINE 0x48 // bss segment line number +#define N_SSYM 0x60 // structure/union element +#define N_SO 0x64 // main source file name +#define N_LSYM 0x80 // stack variable +#define N_BINCL 0x82 // include file beginning +#define N_SOL 0x84 // included source file name +#define N_PSYM 0xa0 // parameter variable +#define N_EINCL 0xa2 // include file end +#define N_ENTRY 0xa4 // alternate entry point +#define N_LBRAC 0xc0 // left bracket +#define N_EXCL 0xc2 // deleted include file +#define N_RBRAC 0xe0 // right bracket +#define N_BCOMM 0xe2 // begin common +#define N_ECOMM 0xe4 // end common +#define N_ECOML 0xe8 // end common (local name) +#define N_LENG 0xfe // length of preceding entry + +/* Entries in the STABS table are formatted as follows. */ +struct stab { + uint32_t n_strx; // index into string table of name + uint8_t n_type; // type of symbol + uint8_t n_other; // misc info (usually empty) + uint16_t n_desc; // description field + uintptr_t n_value; // value of symbol +}; + +#endif /* !__KERN_DEBUG_STAB_H__ */ + diff --git a/code/lab8/kern/driver/clock.c b/code/lab8/kern/driver/clock.c new file mode 100644 index 0000000..5f27ce7 --- /dev/null +++ b/code/lab8/kern/driver/clock.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include + +/* * + * Support for time-related hardware gadgets - the 8253 timer, + * which generates interruptes on IRQ-0. + * */ + +#define IO_TIMER1 0x040 // 8253 Timer #1 + +/* * + * Frequency of all three count-down timers; (TIMER_FREQ/freq) + * is the appropriate count to generate a frequency of freq Hz. + * */ + +#define TIMER_FREQ 1193182 +#define TIMER_DIV(x) ((TIMER_FREQ + (x) / 2) / (x)) + +#define TIMER_MODE (IO_TIMER1 + 3) // timer mode port +#define TIMER_SEL0 0x00 // select counter 0 +#define TIMER_RATEGEN 0x04 // mode 2, rate generator +#define TIMER_16BIT 0x30 // r/w counter 16 bits, LSB first + +volatile size_t ticks; + +long SYSTEM_READ_TIMER( void ){ + return ticks; +} + +/* * + * clock_init - initialize 8253 clock to interrupt 100 times per second, + * and then enable IRQ_TIMER. + * */ +void +clock_init(void) { + // set 8253 timer-chip + outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT); + outb(IO_TIMER1, TIMER_DIV(100) % 256); + outb(IO_TIMER1, TIMER_DIV(100) / 256); + + // initialize time counter 'ticks' to zero + ticks = 0; + + cprintf("++ setup timer interrupts\n"); + pic_enable(IRQ_TIMER); +} + diff --git a/code/lab8/kern/driver/clock.h b/code/lab8/kern/driver/clock.h new file mode 100644 index 0000000..282adb5 --- /dev/null +++ b/code/lab8/kern/driver/clock.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_CLOCK_H__ +#define __KERN_DRIVER_CLOCK_H__ + +#include + +extern volatile size_t ticks; + +void clock_init(void); + +long SYSTEM_READ_TIMER( void ); + + +#endif /* !__KERN_DRIVER_CLOCK_H__ */ + diff --git a/code/lab8/kern/driver/console.c b/code/lab8/kern/driver/console.c new file mode 100644 index 0000000..d4cf56b --- /dev/null +++ b/code/lab8/kern/driver/console.c @@ -0,0 +1,465 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* stupid I/O delay routine necessitated by historical PC design flaws */ +static void +delay(void) { + inb(0x84); + inb(0x84); + inb(0x84); + inb(0x84); +} + +/***** Serial I/O code *****/ +#define COM1 0x3F8 + +#define COM_RX 0 // In: Receive buffer (DLAB=0) +#define COM_TX 0 // Out: Transmit buffer (DLAB=0) +#define COM_DLL 0 // Out: Divisor Latch Low (DLAB=1) +#define COM_DLM 1 // Out: Divisor Latch High (DLAB=1) +#define COM_IER 1 // Out: Interrupt Enable Register +#define COM_IER_RDI 0x01 // Enable receiver data interrupt +#define COM_IIR 2 // In: Interrupt ID Register +#define COM_FCR 2 // Out: FIFO Control Register +#define COM_LCR 3 // Out: Line Control Register +#define COM_LCR_DLAB 0x80 // Divisor latch access bit +#define COM_LCR_WLEN8 0x03 // Wordlength: 8 bits +#define COM_MCR 4 // Out: Modem Control Register +#define COM_MCR_RTS 0x02 // RTS complement +#define COM_MCR_DTR 0x01 // DTR complement +#define COM_MCR_OUT2 0x08 // Out2 complement +#define COM_LSR 5 // In: Line Status Register +#define COM_LSR_DATA 0x01 // Data available +#define COM_LSR_TXRDY 0x20 // Transmit buffer avail +#define COM_LSR_TSRE 0x40 // Transmitter off + +#define MONO_BASE 0x3B4 +#define MONO_BUF 0xB0000 +#define CGA_BASE 0x3D4 +#define CGA_BUF 0xB8000 +#define CRT_ROWS 25 +#define CRT_COLS 80 +#define CRT_SIZE (CRT_ROWS * CRT_COLS) + +#define LPTPORT 0x378 + +static uint16_t *crt_buf; +static uint16_t crt_pos; +static uint16_t addr_6845; + +/* TEXT-mode CGA/VGA display output */ + +static void +cga_init(void) { + volatile uint16_t *cp = (uint16_t *)(CGA_BUF + KERNBASE); + uint16_t was = *cp; + *cp = (uint16_t) 0xA55A; + if (*cp != 0xA55A) { + cp = (uint16_t*)(MONO_BUF + KERNBASE); + addr_6845 = MONO_BASE; + } else { + *cp = was; + addr_6845 = CGA_BASE; + } + + // Extract cursor location + uint32_t pos; + outb(addr_6845, 14); + pos = inb(addr_6845 + 1) << 8; + outb(addr_6845, 15); + pos |= inb(addr_6845 + 1); + + crt_buf = (uint16_t*) cp; + crt_pos = pos; +} + +static bool serial_exists = 0; + +static void +serial_init(void) { + // Turn off the FIFO + outb(COM1 + COM_FCR, 0); + + // Set speed; requires DLAB latch + outb(COM1 + COM_LCR, COM_LCR_DLAB); + outb(COM1 + COM_DLL, (uint8_t) (115200 / 9600)); + outb(COM1 + COM_DLM, 0); + + // 8 data bits, 1 stop bit, parity off; turn off DLAB latch + outb(COM1 + COM_LCR, COM_LCR_WLEN8 & ~COM_LCR_DLAB); + + // No modem controls + outb(COM1 + COM_MCR, 0); + // Enable rcv interrupts + outb(COM1 + COM_IER, COM_IER_RDI); + + // Clear any preexisting overrun indications and interrupts + // Serial port doesn't exist if COM_LSR returns 0xFF + serial_exists = (inb(COM1 + COM_LSR) != 0xFF); + (void) inb(COM1+COM_IIR); + (void) inb(COM1+COM_RX); + + if (serial_exists) { + pic_enable(IRQ_COM1); + } +} + +static void +lpt_putc_sub(int c) { + int i; + for (i = 0; !(inb(LPTPORT + 1) & 0x80) && i < 12800; i ++) { + delay(); + } + outb(LPTPORT + 0, c); + outb(LPTPORT + 2, 0x08 | 0x04 | 0x01); + outb(LPTPORT + 2, 0x08); +} + +/* lpt_putc - copy console output to parallel port */ +static void +lpt_putc(int c) { + if (c != '\b') { + lpt_putc_sub(c); + } + else { + lpt_putc_sub('\b'); + lpt_putc_sub(' '); + lpt_putc_sub('\b'); + } +} + +/* cga_putc - print character to console */ +static void +cga_putc(int c) { + // set black on white + if (!(c & ~0xFF)) { + c |= 0x0700; + } + + switch (c & 0xff) { + case '\b': + if (crt_pos > 0) { + crt_pos --; + crt_buf[crt_pos] = (c & ~0xff) | ' '; + } + break; + case '\n': + crt_pos += CRT_COLS; + case '\r': + crt_pos -= (crt_pos % CRT_COLS); + break; + default: + crt_buf[crt_pos ++] = c; // write the character + break; + } + + // What is the purpose of this? + if (crt_pos >= CRT_SIZE) { + int i; + memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t)); + for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i ++) { + crt_buf[i] = 0x0700 | ' '; + } + crt_pos -= CRT_COLS; + } + + // move that little blinky thing + outb(addr_6845, 14); + outb(addr_6845 + 1, crt_pos >> 8); + outb(addr_6845, 15); + outb(addr_6845 + 1, crt_pos); +} + +static void +serial_putc_sub(int c) { + int i; + for (i = 0; !(inb(COM1 + COM_LSR) & COM_LSR_TXRDY) && i < 12800; i ++) { + delay(); + } + outb(COM1 + COM_TX, c); +} + +/* serial_putc - print character to serial port */ +static void +serial_putc(int c) { + if (c != '\b') { + serial_putc_sub(c); + } + else { + serial_putc_sub('\b'); + serial_putc_sub(' '); + serial_putc_sub('\b'); + } +} + +/* * + * Here we manage the console input buffer, where we stash characters + * received from the keyboard or serial port whenever the corresponding + * interrupt occurs. + * */ + +#define CONSBUFSIZE 512 + +static struct { + uint8_t buf[CONSBUFSIZE]; + uint32_t rpos; + uint32_t wpos; +} cons; + +/* * + * cons_intr - called by device interrupt routines to feed input + * characters into the circular console input buffer. + * */ +static void +cons_intr(int (*proc)(void)) { + int c; + while ((c = (*proc)()) != -1) { + if (c != 0) { + cons.buf[cons.wpos ++] = c; + if (cons.wpos == CONSBUFSIZE) { + cons.wpos = 0; + } + } + } +} + +/* serial_proc_data - get data from serial port */ +static int +serial_proc_data(void) { + if (!(inb(COM1 + COM_LSR) & COM_LSR_DATA)) { + return -1; + } + int c = inb(COM1 + COM_RX); + if (c == 127) { + c = '\b'; + } + return c; +} + +/* serial_intr - try to feed input characters from serial port */ +void +serial_intr(void) { + if (serial_exists) { + cons_intr(serial_proc_data); + } +} + +/***** Keyboard input code *****/ + +#define NO 0 + +#define SHIFT (1<<0) +#define CTL (1<<1) +#define ALT (1<<2) + +#define CAPSLOCK (1<<3) +#define NUMLOCK (1<<4) +#define SCROLLLOCK (1<<5) + +#define E0ESC (1<<6) + +static uint8_t shiftcode[256] = { + [0x1D] CTL, + [0x2A] SHIFT, + [0x36] SHIFT, + [0x38] ALT, + [0x9D] CTL, + [0xB8] ALT +}; + +static uint8_t togglecode[256] = { + [0x3A] CAPSLOCK, + [0x45] NUMLOCK, + [0x46] SCROLLLOCK +}; + +static uint8_t normalmap[256] = { + NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 + '7', '8', '9', '0', '-', '=', '\b', '\t', + 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 + 'o', 'p', '[', ']', '\n', NO, 'a', 's', + 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 + '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t shiftmap[256] = { + NO, 033, '!', '@', '#', '$', '%', '^', // 0x00 + '&', '*', '(', ')', '_', '+', '\b', '\t', + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 + 'O', 'P', '{', '}', '\n', NO, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 + '"', '~', NO, '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', NO, '*', // 0x30 + NO, ' ', NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 + '8', '9', '-', '4', '5', '6', '+', '1', + '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 + [0xC7] KEY_HOME, [0x9C] '\n' /*KP_Enter*/, + [0xB5] '/' /*KP_Div*/, [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +#define C(x) (x - '@') + +static uint8_t ctlmap[256] = { + NO, NO, NO, NO, NO, NO, NO, NO, + NO, NO, NO, NO, NO, NO, NO, NO, + C('Q'), C('W'), C('E'), C('R'), C('T'), C('Y'), C('U'), C('I'), + C('O'), C('P'), NO, NO, '\r', NO, C('A'), C('S'), + C('D'), C('F'), C('G'), C('H'), C('J'), C('K'), C('L'), NO, + NO, NO, NO, C('\\'), C('Z'), C('X'), C('C'), C('V'), + C('B'), C('N'), C('M'), NO, NO, C('/'), NO, NO, + [0x97] KEY_HOME, + [0xB5] C('/'), [0xC8] KEY_UP, + [0xC9] KEY_PGUP, [0xCB] KEY_LF, + [0xCD] KEY_RT, [0xCF] KEY_END, + [0xD0] KEY_DN, [0xD1] KEY_PGDN, + [0xD2] KEY_INS, [0xD3] KEY_DEL +}; + +static uint8_t *charcode[4] = { + normalmap, + shiftmap, + ctlmap, + ctlmap +}; + +/* * + * kbd_proc_data - get data from keyboard + * + * The kbd_proc_data() function gets data from the keyboard. + * If we finish a character, return it, else 0. And return -1 if no data. + * */ +static int +kbd_proc_data(void) { + int c; + uint8_t data; + static uint32_t shift; + + if ((inb(KBSTATP) & KBS_DIB) == 0) { + return -1; + } + + data = inb(KBDATAP); + + if (data == 0xE0) { + // E0 escape character + shift |= E0ESC; + return 0; + } else if (data & 0x80) { + // Key released + data = (shift & E0ESC ? data : data & 0x7F); + shift &= ~(shiftcode[data] | E0ESC); + return 0; + } else if (shift & E0ESC) { + // Last character was an E0 escape; or with 0x80 + data |= 0x80; + shift &= ~E0ESC; + } + + shift |= shiftcode[data]; + shift ^= togglecode[data]; + + c = charcode[shift & (CTL | SHIFT)][data]; + if (shift & CAPSLOCK) { + if ('a' <= c && c <= 'z') + c += 'A' - 'a'; + else if ('A' <= c && c <= 'Z') + c += 'a' - 'A'; + } + + // Process special keys + // Ctrl-Alt-Del: reboot + if (!(~shift & (CTL | ALT)) && c == KEY_DEL) { + cprintf("Rebooting!\n"); + outb(0x92, 0x3); // courtesy of Chris Frost + } + return c; +} + +/* kbd_intr - try to feed input characters from keyboard */ +static void +kbd_intr(void) { + cons_intr(kbd_proc_data); +} + +static void +kbd_init(void) { + // drain the kbd buffer + kbd_intr(); + pic_enable(IRQ_KBD); +} + +/* cons_init - initializes the console devices */ +void +cons_init(void) { + cga_init(); + serial_init(); + kbd_init(); + if (!serial_exists) { + cprintf("serial port does not exist!!\n"); + } +} + +/* cons_putc - print a single character @c to console devices */ +void +cons_putc(int c) { + bool intr_flag; + local_intr_save(intr_flag); + { + lpt_putc(c); + cga_putc(c); + serial_putc(c); + } + local_intr_restore(intr_flag); +} + +/* * + * cons_getc - return the next input character from console, + * or 0 if none waiting. + * */ +int +cons_getc(void) { + int c = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + // poll for any pending input characters, + // so that this function works even when interrupts are disabled + // (e.g., when called from the kernel monitor). + serial_intr(); + kbd_intr(); + + // grab the next character from the input buffer. + if (cons.rpos != cons.wpos) { + c = cons.buf[cons.rpos ++]; + if (cons.rpos == CONSBUFSIZE) { + cons.rpos = 0; + } + } + } + local_intr_restore(intr_flag); + return c; +} + diff --git a/code/lab8/kern/driver/console.h b/code/lab8/kern/driver/console.h new file mode 100644 index 0000000..72e6167 --- /dev/null +++ b/code/lab8/kern/driver/console.h @@ -0,0 +1,11 @@ +#ifndef __KERN_DRIVER_CONSOLE_H__ +#define __KERN_DRIVER_CONSOLE_H__ + +void cons_init(void); +void cons_putc(int c); +int cons_getc(void); +void serial_intr(void); +void kbd_intr(void); + +#endif /* !__KERN_DRIVER_CONSOLE_H__ */ + diff --git a/code/lab8/kern/driver/ide.c b/code/lab8/kern/driver/ide.c new file mode 100644 index 0000000..271cfa8 --- /dev/null +++ b/code/lab8/kern/driver/ide.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ISA_DATA 0x00 +#define ISA_ERROR 0x01 +#define ISA_PRECOMP 0x01 +#define ISA_CTRL 0x02 +#define ISA_SECCNT 0x02 +#define ISA_SECTOR 0x03 +#define ISA_CYL_LO 0x04 +#define ISA_CYL_HI 0x05 +#define ISA_SDH 0x06 +#define ISA_COMMAND 0x07 +#define ISA_STATUS 0x07 + +#define IDE_BSY 0x80 +#define IDE_DRDY 0x40 +#define IDE_DF 0x20 +#define IDE_DRQ 0x08 +#define IDE_ERR 0x01 + +#define IDE_CMD_READ 0x20 +#define IDE_CMD_WRITE 0x30 +#define IDE_CMD_IDENTIFY 0xEC + +#define IDE_IDENT_SECTORS 20 +#define IDE_IDENT_MODEL 54 +#define IDE_IDENT_CAPABILITIES 98 +#define IDE_IDENT_CMDSETS 164 +#define IDE_IDENT_MAX_LBA 120 +#define IDE_IDENT_MAX_LBA_EXT 200 + +#define IO_BASE0 0x1F0 +#define IO_BASE1 0x170 +#define IO_CTRL0 0x3F4 +#define IO_CTRL1 0x374 + +#define MAX_IDE 4 +#define MAX_NSECS 128 +#define MAX_DISK_NSECS 0x10000000U +#define VALID_IDE(ideno) (((ideno) >= 0) && ((ideno) < MAX_IDE) && (ide_devices[ideno].valid)) + +static const struct { + unsigned short base; // I/O Base + unsigned short ctrl; // Control Base +} channels[2] = { + {IO_BASE0, IO_CTRL0}, + {IO_BASE1, IO_CTRL1}, +}; + +#define IO_BASE(ideno) (channels[(ideno) >> 1].base) +#define IO_CTRL(ideno) (channels[(ideno) >> 1].ctrl) + +static struct ide_device { + unsigned char valid; // 0 or 1 (If Device Really Exists) + unsigned int sets; // Commend Sets Supported + unsigned int size; // Size in Sectors + unsigned char model[41]; // Model in String +} ide_devices[MAX_IDE]; + +static int +ide_wait_ready(unsigned short iobase, bool check_error) { + int r; + while ((r = inb(iobase + ISA_STATUS)) & IDE_BSY) + /* nothing */; + if (check_error && (r & (IDE_DF | IDE_ERR)) != 0) { + return -1; + } + return 0; +} + +void +ide_init(void) { + static_assert((SECTSIZE % 4) == 0); + unsigned short ideno, iobase; + for (ideno = 0; ideno < MAX_IDE; ideno ++) { + /* assume that no device here */ + ide_devices[ideno].valid = 0; + + iobase = IO_BASE(ideno); + + /* wait device ready */ + ide_wait_ready(iobase, 0); + + /* step1: select drive */ + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4)); + ide_wait_ready(iobase, 0); + + /* step2: send ATA identify command */ + outb(iobase + ISA_COMMAND, IDE_CMD_IDENTIFY); + ide_wait_ready(iobase, 0); + + /* step3: polling */ + if (inb(iobase + ISA_STATUS) == 0 || ide_wait_ready(iobase, 1) != 0) { + continue ; + } + + /* device is ok */ + ide_devices[ideno].valid = 1; + + /* read identification space of the device */ + unsigned int buffer[128]; + insl(iobase + ISA_DATA, buffer, sizeof(buffer) / sizeof(unsigned int)); + + unsigned char *ident = (unsigned char *)buffer; + unsigned int sectors; + unsigned int cmdsets = *(unsigned int *)(ident + IDE_IDENT_CMDSETS); + /* device use 48-bits or 28-bits addressing */ + if (cmdsets & (1 << 26)) { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA_EXT); + } + else { + sectors = *(unsigned int *)(ident + IDE_IDENT_MAX_LBA); + } + ide_devices[ideno].sets = cmdsets; + ide_devices[ideno].size = sectors; + + /* check if supports LBA */ + assert((*(unsigned short *)(ident + IDE_IDENT_CAPABILITIES) & 0x200) != 0); + + unsigned char *model = ide_devices[ideno].model, *data = ident + IDE_IDENT_MODEL; + unsigned int i, length = 40; + for (i = 0; i < length; i += 2) { + model[i] = data[i + 1], model[i + 1] = data[i]; + } + do { + model[i] = '\0'; + } while (i -- > 0 && model[i] == ' '); + + cprintf("ide %d: %10u(sectors), '%s'.\n", ideno, ide_devices[ideno].size, ide_devices[ideno].model); + } + + // enable ide interrupt + pic_enable(IRQ_IDE1); + pic_enable(IRQ_IDE2); +} + +bool +ide_device_valid(unsigned short ideno) { + return VALID_IDE(ideno); +} + +size_t +ide_device_size(unsigned short ideno) { + if (ide_device_valid(ideno)) { + return ide_devices[ideno].size; + } + return 0; +} + +int +ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_READ); + + int ret = 0; + for (; nsecs > 0; nsecs --, dst += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + insl(iobase, dst, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + +int +ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs) { + assert(nsecs <= MAX_NSECS && VALID_IDE(ideno)); + assert(secno < MAX_DISK_NSECS && secno + nsecs <= MAX_DISK_NSECS); + unsigned short iobase = IO_BASE(ideno), ioctrl = IO_CTRL(ideno); + + ide_wait_ready(iobase, 0); + + // generate interrupt + outb(ioctrl + ISA_CTRL, 0); + outb(iobase + ISA_SECCNT, nsecs); + outb(iobase + ISA_SECTOR, secno & 0xFF); + outb(iobase + ISA_CYL_LO, (secno >> 8) & 0xFF); + outb(iobase + ISA_CYL_HI, (secno >> 16) & 0xFF); + outb(iobase + ISA_SDH, 0xE0 | ((ideno & 1) << 4) | ((secno >> 24) & 0xF)); + outb(iobase + ISA_COMMAND, IDE_CMD_WRITE); + + int ret = 0; + for (; nsecs > 0; nsecs --, src += SECTSIZE) { + if ((ret = ide_wait_ready(iobase, 1)) != 0) { + goto out; + } + outsl(iobase, src, SECTSIZE / sizeof(uint32_t)); + } + +out: + return ret; +} + diff --git a/code/lab8/kern/driver/ide.h b/code/lab8/kern/driver/ide.h new file mode 100644 index 0000000..3e3fd21 --- /dev/null +++ b/code/lab8/kern/driver/ide.h @@ -0,0 +1,14 @@ +#ifndef __KERN_DRIVER_IDE_H__ +#define __KERN_DRIVER_IDE_H__ + +#include + +void ide_init(void); +bool ide_device_valid(unsigned short ideno); +size_t ide_device_size(unsigned short ideno); + +int ide_read_secs(unsigned short ideno, uint32_t secno, void *dst, size_t nsecs); +int ide_write_secs(unsigned short ideno, uint32_t secno, const void *src, size_t nsecs); + +#endif /* !__KERN_DRIVER_IDE_H__ */ + diff --git a/code/lab8/kern/driver/intr.c b/code/lab8/kern/driver/intr.c new file mode 100644 index 0000000..e64da62 --- /dev/null +++ b/code/lab8/kern/driver/intr.c @@ -0,0 +1,15 @@ +#include +#include + +/* intr_enable - enable irq interrupt */ +void +intr_enable(void) { + sti(); +} + +/* intr_disable - disable irq interrupt */ +void +intr_disable(void) { + cli(); +} + diff --git a/code/lab8/kern/driver/intr.h b/code/lab8/kern/driver/intr.h new file mode 100644 index 0000000..5fdf7a5 --- /dev/null +++ b/code/lab8/kern/driver/intr.h @@ -0,0 +1,8 @@ +#ifndef __KERN_DRIVER_INTR_H__ +#define __KERN_DRIVER_INTR_H__ + +void intr_enable(void); +void intr_disable(void); + +#endif /* !__KERN_DRIVER_INTR_H__ */ + diff --git a/code/lab8/kern/driver/kbdreg.h b/code/lab8/kern/driver/kbdreg.h new file mode 100644 index 0000000..00dc49a --- /dev/null +++ b/code/lab8/kern/driver/kbdreg.h @@ -0,0 +1,84 @@ +#ifndef __KERN_DRIVER_KBDREG_H__ +#define __KERN_DRIVER_KBDREG_H__ + +// Special keycodes +#define KEY_HOME 0xE0 +#define KEY_END 0xE1 +#define KEY_UP 0xE2 +#define KEY_DN 0xE3 +#define KEY_LF 0xE4 +#define KEY_RT 0xE5 +#define KEY_PGUP 0xE6 +#define KEY_PGDN 0xE7 +#define KEY_INS 0xE8 +#define KEY_DEL 0xE9 + + +/* This is i8042reg.h + kbdreg.h from NetBSD. */ + +#define KBSTATP 0x64 // kbd controller status port(I) +#define KBS_DIB 0x01 // kbd data in buffer +#define KBS_IBF 0x02 // kbd input buffer low +#define KBS_WARM 0x04 // kbd input buffer low +#define BS_OCMD 0x08 // kbd output buffer has command +#define KBS_NOSEC 0x10 // kbd security lock not engaged +#define KBS_TERR 0x20 // kbd transmission error +#define KBS_RERR 0x40 // kbd receive error +#define KBS_PERR 0x80 // kbd parity error + +#define KBCMDP 0x64 // kbd controller port(O) +#define KBC_RAMREAD 0x20 // read from RAM +#define KBC_RAMWRITE 0x60 // write to RAM +#define KBC_AUXDISABLE 0xa7 // disable auxiliary port +#define KBC_AUXENABLE 0xa8 // enable auxiliary port +#define KBC_AUXTEST 0xa9 // test auxiliary port +#define KBC_KBDECHO 0xd2 // echo to keyboard port +#define KBC_AUXECHO 0xd3 // echo to auxiliary port +#define KBC_AUXWRITE 0xd4 // write to auxiliary port +#define KBC_SELFTEST 0xaa // start self-test +#define KBC_KBDTEST 0xab // test keyboard port +#define KBC_KBDDISABLE 0xad // disable keyboard port +#define KBC_KBDENABLE 0xae // enable keyboard port +#define KBC_PULSE0 0xfe // pulse output bit 0 +#define KBC_PULSE1 0xfd // pulse output bit 1 +#define KBC_PULSE2 0xfb // pulse output bit 2 +#define KBC_PULSE3 0xf7 // pulse output bit 3 + +#define KBDATAP 0x60 // kbd data port(I) +#define KBOUTP 0x60 // kbd data port(O) + +#define K_RDCMDBYTE 0x20 +#define K_LDCMDBYTE 0x60 + +#define KC8_TRANS 0x40 // convert to old scan codes +#define KC8_MDISABLE 0x20 // disable mouse +#define KC8_KDISABLE 0x10 // disable keyboard +#define KC8_IGNSEC 0x08 // ignore security lock +#define KC8_CPU 0x04 // exit from protected mode reset +#define KC8_MENABLE 0x02 // enable mouse interrupt +#define KC8_KENABLE 0x01 // enable keyboard interrupt +#define CMDBYTE (KC8_TRANS|KC8_CPU|KC8_MENABLE|KC8_KENABLE) + +/* keyboard commands */ +#define KBC_RESET 0xFF // reset the keyboard +#define KBC_RESEND 0xFE // request the keyboard resend the last byte +#define KBC_SETDEFAULT 0xF6 // resets keyboard to its power-on defaults +#define KBC_DISABLE 0xF5 // as per KBC_SETDEFAULT, but also disable key scanning +#define KBC_ENABLE 0xF4 // enable key scanning +#define KBC_TYPEMATIC 0xF3 // set typematic rate and delay +#define KBC_SETTABLE 0xF0 // set scancode translation table +#define KBC_MODEIND 0xED // set mode indicators(i.e. LEDs) +#define KBC_ECHO 0xEE // request an echo from the keyboard + +/* keyboard responses */ +#define KBR_EXTENDED 0xE0 // extended key sequence +#define KBR_RESEND 0xFE // needs resend of command +#define KBR_ACK 0xFA // received a valid command +#define KBR_OVERRUN 0x00 // flooded +#define KBR_FAILURE 0xFD // diagnosic failure +#define KBR_BREAK 0xF0 // break code prefix - sent on key release +#define KBR_RSTDONE 0xAA // reset complete +#define KBR_ECHO 0xEE // echo response + +#endif /* !__KERN_DRIVER_KBDREG_H__ */ + diff --git a/code/lab8/kern/driver/picirq.c b/code/lab8/kern/driver/picirq.c new file mode 100644 index 0000000..e7f7063 --- /dev/null +++ b/code/lab8/kern/driver/picirq.c @@ -0,0 +1,86 @@ +#include +#include +#include + +// I/O Addresses of the two programmable interrupt controllers +#define IO_PIC1 0x20 // Master (IRQs 0-7) +#define IO_PIC2 0xA0 // Slave (IRQs 8-15) + +#define IRQ_SLAVE 2 // IRQ at which slave connects to master + +// Current IRQ mask. +// Initial IRQ mask has interrupt 2 enabled (for slave 8259A). +static uint16_t irq_mask = 0xFFFF & ~(1 << IRQ_SLAVE); +static bool did_init = 0; + +static void +pic_setmask(uint16_t mask) { + irq_mask = mask; + if (did_init) { + outb(IO_PIC1 + 1, mask); + outb(IO_PIC2 + 1, mask >> 8); + } +} + +void +pic_enable(unsigned int irq) { + pic_setmask(irq_mask & ~(1 << irq)); +} + +/* pic_init - initialize the 8259A interrupt controllers */ +void +pic_init(void) { + did_init = 1; + + // mask all interrupts + outb(IO_PIC1 + 1, 0xFF); + outb(IO_PIC2 + 1, 0xFF); + + // Set up master (8259A-1) + + // ICW1: 0001g0hi + // g: 0 = edge triggering, 1 = level triggering + // h: 0 = cascaded PICs, 1 = master only + // i: 0 = no ICW4, 1 = ICW4 required + outb(IO_PIC1, 0x11); + + // ICW2: Vector offset + outb(IO_PIC1 + 1, IRQ_OFFSET); + + // ICW3: (master PIC) bit mask of IR lines connected to slaves + // (slave PIC) 3-bit # of slave's connection to master + outb(IO_PIC1 + 1, 1 << IRQ_SLAVE); + + // ICW4: 000nbmap + // n: 1 = special fully nested mode + // b: 1 = buffered mode + // m: 0 = slave PIC, 1 = master PIC + // (ignored when b is 0, as the master/slave role + // can be hardwired). + // a: 1 = Automatic EOI mode + // p: 0 = MCS-80/85 mode, 1 = intel x86 mode + outb(IO_PIC1 + 1, 0x3); + + // Set up slave (8259A-2) + outb(IO_PIC2, 0x11); // ICW1 + outb(IO_PIC2 + 1, IRQ_OFFSET + 8); // ICW2 + outb(IO_PIC2 + 1, IRQ_SLAVE); // ICW3 + // NB Automatic EOI mode doesn't tend to work on the slave. + // Linux source code says it's "to be investigated". + outb(IO_PIC2 + 1, 0x3); // ICW4 + + // OCW3: 0ef01prs + // ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask + // p: 0 = no polling, 1 = polling mode + // rs: 0x = NOP, 10 = read IRR, 11 = read ISR + outb(IO_PIC1, 0x68); // clear specific mask + outb(IO_PIC1, 0x0a); // read IRR by default + + outb(IO_PIC2, 0x68); // OCW3 + outb(IO_PIC2, 0x0a); // OCW3 + + if (irq_mask != 0xFFFF) { + pic_setmask(irq_mask); + } +} + diff --git a/code/lab8/kern/driver/picirq.h b/code/lab8/kern/driver/picirq.h new file mode 100644 index 0000000..b61e72e --- /dev/null +++ b/code/lab8/kern/driver/picirq.h @@ -0,0 +1,10 @@ +#ifndef __KERN_DRIVER_PICIRQ_H__ +#define __KERN_DRIVER_PICIRQ_H__ + +void pic_init(void); +void pic_enable(unsigned int irq); + +#define IRQ_OFFSET 32 + +#endif /* !__KERN_DRIVER_PICIRQ_H__ */ + diff --git a/code/lab8/kern/fs/devs/dev.c b/code/lab8/kern/fs/devs/dev.c new file mode 100644 index 0000000..007d748 --- /dev/null +++ b/code/lab8/kern/fs/devs/dev.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include + +/* + * dev_open - Called for each open(). + */ +static int +dev_open(struct inode *node, uint32_t open_flags) { + if (open_flags & (O_CREAT | O_TRUNC | O_EXCL | O_APPEND)) { + return -E_INVAL; + } + struct device *dev = vop_info(node, device); + return dop_open(dev, open_flags); +} + +/* + * dev_close - Called on the last close(). Just pass through. + */ +static int +dev_close(struct inode *node) { + struct device *dev = vop_info(node, device); + return dop_close(dev); +} + +/* + * dev_read -Called for read. Hand off to iobuf. + */ +static int +dev_read(struct inode *node, struct iobuf *iob) { + struct device *dev = vop_info(node, device); + return dop_io(dev, iob, 0); +} + +/* + * dev_write -Called for write. Hand off to iobuf. + */ +static int +dev_write(struct inode *node, struct iobuf *iob) { + struct device *dev = vop_info(node, device); + return dop_io(dev, iob, 1); +} + +/* + * dev_ioctl - Called for ioctl(). Just pass through. + */ +static int +dev_ioctl(struct inode *node, int op, void *data) { + struct device *dev = vop_info(node, device); + return dop_ioctl(dev, op, data); +} + +/* + * dev_fstat - Called for stat(). + * Set the type and the size (block devices only). + * The link count for a device is always 1. + */ +static int +dev_fstat(struct inode *node, struct stat *stat) { + int ret; + memset(stat, 0, sizeof(struct stat)); + if ((ret = vop_gettype(node, &(stat->st_mode))) != 0) { + return ret; + } + struct device *dev = vop_info(node, device); + stat->st_nlinks = 1; + stat->st_blocks = dev->d_blocks; + stat->st_size = stat->st_blocks * dev->d_blocksize; + return 0; +} + +/* + * dev_gettype - Return the type. A device is a "block device" if it has a known + * length. A device that generates data in a stream is a "character + * device". + */ +static int +dev_gettype(struct inode *node, uint32_t *type_store) { + struct device *dev = vop_info(node, device); + *type_store = (dev->d_blocks > 0) ? S_IFBLK : S_IFCHR; + return 0; +} + +/* + * dev_tryseek - Attempt a seek. + * For block devices, require block alignment. + * For character devices, prohibit seeking entirely. + */ +static int +dev_tryseek(struct inode *node, off_t pos) { + struct device *dev = vop_info(node, device); + if (dev->d_blocks > 0) { + if ((pos % dev->d_blocksize) == 0) { + if (pos >= 0 && pos < dev->d_blocks * dev->d_blocksize) { + return 0; + } + } + } + return -E_INVAL; +} + +/* + * dev_lookup - Name lookup. + * + * One interesting feature of device:name pathname syntax is that you + * can implement pathnames on arbitrary devices. For instance, if you + * had a graphics device that supported multiple resolutions (which we + * don't), you might arrange things so that you could open it with + * pathnames like "video:800x600/24bpp" in order to select the operating + * mode. + * + * However, we have no support for this in the base system. + */ +static int +dev_lookup(struct inode *node, char *path, struct inode **node_store) { + if (*path != '\0') { + return -E_NOENT; + } + vop_ref_inc(node); + *node_store = node; + return 0; +} + +/* + * Function table for device inodes. + */ +static const struct inode_ops dev_node_ops = { + .vop_magic = VOP_MAGIC, + .vop_open = dev_open, + .vop_close = dev_close, + .vop_read = dev_read, + .vop_write = dev_write, + .vop_fstat = dev_fstat, + .vop_ioctl = dev_ioctl, + .vop_gettype = dev_gettype, + .vop_tryseek = dev_tryseek, + .vop_lookup = dev_lookup, +}; + +#define init_device(x) \ + do { \ + extern void dev_init_##x(void); \ + dev_init_##x(); \ + } while (0) + +/* dev_init - Initialization functions for builtin vfs-level devices. */ +void +dev_init(void) { + // init_device(null); + init_device(stdin); + init_device(stdout); + init_device(disk0); +} +/* dev_create_inode - Create inode for a vfs-level device. */ +struct inode * +dev_create_inode(void) { + struct inode *node; + if ((node = alloc_inode(device)) != NULL) { + vop_init(node, &dev_node_ops, NULL); + } + return node; +} + diff --git a/code/lab8/kern/fs/devs/dev.h b/code/lab8/kern/fs/devs/dev.h new file mode 100644 index 0000000..9c605d7 --- /dev/null +++ b/code/lab8/kern/fs/devs/dev.h @@ -0,0 +1,31 @@ +#ifndef __KERN_FS_DEVS_DEV_H__ +#define __KERN_FS_DEVS_DEV_H__ + +#include + +struct inode; +struct iobuf; + +/* + * Filesystem-namespace-accessible device. + * d_io is for both reads and writes; the iobuf will indicates the direction. + */ +struct device { + size_t d_blocks; + size_t d_blocksize; + int (*d_open)(struct device *dev, uint32_t open_flags); + int (*d_close)(struct device *dev); + int (*d_io)(struct device *dev, struct iobuf *iob, bool write); + int (*d_ioctl)(struct device *dev, int op, void *data); +}; + +#define dop_open(dev, open_flags) ((dev)->d_open(dev, open_flags)) +#define dop_close(dev) ((dev)->d_close(dev)) +#define dop_io(dev, iob, write) ((dev)->d_io(dev, iob, write)) +#define dop_ioctl(dev, op, data) ((dev)->d_ioctl(dev, op, data)) + +void dev_init(void); +struct inode *dev_create_inode(void); + +#endif /* !__KERN_FS_DEVS_DEV_H__ */ + diff --git a/code/lab8/kern/fs/devs/dev_disk0.c b/code/lab8/kern/fs/devs/dev_disk0.c new file mode 100644 index 0000000..3d6fc1d --- /dev/null +++ b/code/lab8/kern/fs/devs/dev_disk0.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DISK0_BLKSIZE PGSIZE +#define DISK0_BUFSIZE (4 * DISK0_BLKSIZE) +#define DISK0_BLK_NSECT (DISK0_BLKSIZE / SECTSIZE) + +static char *disk0_buffer; +static semaphore_t disk0_sem; + +static void +lock_disk0(void) { + down(&(disk0_sem)); +} + +static void +unlock_disk0(void) { + up(&(disk0_sem)); +} + +static int +disk0_open(struct device *dev, uint32_t open_flags) { + return 0; +} + +static int +disk0_close(struct device *dev) { + return 0; +} + +static void +disk0_read_blks_nolock(uint32_t blkno, uint32_t nblks) { + int ret; + uint32_t sectno = blkno * DISK0_BLK_NSECT, nsecs = nblks * DISK0_BLK_NSECT; + if ((ret = ide_read_secs(DISK0_DEV_NO, sectno, disk0_buffer, nsecs)) != 0) { + panic("disk0: read blkno = %d (sectno = %d), nblks = %d (nsecs = %d): 0x%08x.\n", + blkno, sectno, nblks, nsecs, ret); + } +} + +static void +disk0_write_blks_nolock(uint32_t blkno, uint32_t nblks) { + int ret; + uint32_t sectno = blkno * DISK0_BLK_NSECT, nsecs = nblks * DISK0_BLK_NSECT; + if ((ret = ide_write_secs(DISK0_DEV_NO, sectno, disk0_buffer, nsecs)) != 0) { + panic("disk0: write blkno = %d (sectno = %d), nblks = %d (nsecs = %d): 0x%08x.\n", + blkno, sectno, nblks, nsecs, ret); + } +} + +static int +disk0_io(struct device *dev, struct iobuf *iob, bool write) { + off_t offset = iob->io_offset; + size_t resid = iob->io_resid; + uint32_t blkno = offset / DISK0_BLKSIZE; + uint32_t nblks = resid / DISK0_BLKSIZE; + + /* don't allow I/O that isn't block-aligned */ + if ((offset % DISK0_BLKSIZE) != 0 || (resid % DISK0_BLKSIZE) != 0) { + return -E_INVAL; + } + + /* don't allow I/O past the end of disk0 */ + if (blkno + nblks > dev->d_blocks) { + return -E_INVAL; + } + + /* read/write nothing ? */ + if (nblks == 0) { + return 0; + } + + lock_disk0(); + while (resid != 0) { + size_t copied, alen = DISK0_BUFSIZE; + if (write) { + iobuf_move(iob, disk0_buffer, alen, 0, &copied); + assert(copied != 0 && copied <= resid && copied % DISK0_BLKSIZE == 0); + nblks = copied / DISK0_BLKSIZE; + disk0_write_blks_nolock(blkno, nblks); + } + else { + if (alen > resid) { + alen = resid; + } + nblks = alen / DISK0_BLKSIZE; + disk0_read_blks_nolock(blkno, nblks); + iobuf_move(iob, disk0_buffer, alen, 1, &copied); + assert(copied == alen && copied % DISK0_BLKSIZE == 0); + } + resid -= copied, blkno += nblks; + } + unlock_disk0(); + return 0; +} + +static int +disk0_ioctl(struct device *dev, int op, void *data) { + return -E_UNIMP; +} + +static void +disk0_device_init(struct device *dev) { + static_assert(DISK0_BLKSIZE % SECTSIZE == 0); + if (!ide_device_valid(DISK0_DEV_NO)) { + panic("disk0 device isn't available.\n"); + } + dev->d_blocks = ide_device_size(DISK0_DEV_NO) / DISK0_BLK_NSECT; + dev->d_blocksize = DISK0_BLKSIZE; + dev->d_open = disk0_open; + dev->d_close = disk0_close; + dev->d_io = disk0_io; + dev->d_ioctl = disk0_ioctl; + sem_init(&(disk0_sem), 1); + + static_assert(DISK0_BUFSIZE % DISK0_BLKSIZE == 0); + if ((disk0_buffer = kmalloc(DISK0_BUFSIZE)) == NULL) { + panic("disk0 alloc buffer failed.\n"); + } +} + +void +dev_init_disk0(void) { + struct inode *node; + if ((node = dev_create_inode()) == NULL) { + panic("disk0: dev_create_node.\n"); + } + disk0_device_init(vop_info(node, device)); + + int ret; + if ((ret = vfs_add_dev("disk0", node, 1)) != 0) { + panic("disk0: vfs_add_dev: %e.\n", ret); + } +} + diff --git a/code/lab8/kern/fs/devs/dev_stdin.c b/code/lab8/kern/fs/devs/dev_stdin.c new file mode 100644 index 0000000..0da00fc --- /dev/null +++ b/code/lab8/kern/fs/devs/dev_stdin.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STDIN_BUFSIZE 4096 + +static char stdin_buffer[STDIN_BUFSIZE]; +static off_t p_rpos, p_wpos; +static wait_queue_t __wait_queue, *wait_queue = &__wait_queue; + +void +dev_stdin_write(char c) { + bool intr_flag; + if (c != '\0') { + local_intr_save(intr_flag); + { + stdin_buffer[p_wpos % STDIN_BUFSIZE] = c; + if (p_wpos - p_rpos < STDIN_BUFSIZE) { + p_wpos ++; + } + if (!wait_queue_empty(wait_queue)) { + wakeup_queue(wait_queue, WT_KBD, 1); + } + } + local_intr_restore(intr_flag); + } +} + +static int +dev_stdin_read(char *buf, size_t len) { + int ret = 0; + bool intr_flag; + local_intr_save(intr_flag); + { + for (; ret < len; ret ++, p_rpos ++) { + try_again: + if (p_rpos < p_wpos) { + *buf ++ = stdin_buffer[p_rpos % STDIN_BUFSIZE]; + } + else { + wait_t __wait, *wait = &__wait; + wait_current_set(wait_queue, wait, WT_KBD); + local_intr_restore(intr_flag); + + schedule(); + + local_intr_save(intr_flag); + wait_current_del(wait_queue, wait); + if (wait->wakeup_flags == WT_KBD) { + goto try_again; + } + break; + } + } + } + local_intr_restore(intr_flag); + return ret; +} + +static int +stdin_open(struct device *dev, uint32_t open_flags) { + if (open_flags != O_RDONLY) { + return -E_INVAL; + } + return 0; +} + +static int +stdin_close(struct device *dev) { + return 0; +} + +static int +stdin_io(struct device *dev, struct iobuf *iob, bool write) { + if (!write) { + int ret; + if ((ret = dev_stdin_read(iob->io_base, iob->io_resid)) > 0) { + iob->io_resid -= ret; + } + return ret; + } + return -E_INVAL; +} + +static int +stdin_ioctl(struct device *dev, int op, void *data) { + return -E_INVAL; +} + +static void +stdin_device_init(struct device *dev) { + dev->d_blocks = 0; + dev->d_blocksize = 1; + dev->d_open = stdin_open; + dev->d_close = stdin_close; + dev->d_io = stdin_io; + dev->d_ioctl = stdin_ioctl; + + p_rpos = p_wpos = 0; + wait_queue_init(wait_queue); +} + +void +dev_init_stdin(void) { + struct inode *node; + if ((node = dev_create_inode()) == NULL) { + panic("stdin: dev_create_node.\n"); + } + stdin_device_init(vop_info(node, device)); + + int ret; + if ((ret = vfs_add_dev("stdin", node, 0)) != 0) { + panic("stdin: vfs_add_dev: %e.\n", ret); + } +} + diff --git a/code/lab8/kern/fs/devs/dev_stdout.c b/code/lab8/kern/fs/devs/dev_stdout.c new file mode 100644 index 0000000..641acf0 --- /dev/null +++ b/code/lab8/kern/fs/devs/dev_stdout.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +stdout_open(struct device *dev, uint32_t open_flags) { + if (open_flags != O_WRONLY) { + return -E_INVAL; + } + return 0; +} + +static int +stdout_close(struct device *dev) { + return 0; +} + +static int +stdout_io(struct device *dev, struct iobuf *iob, bool write) { + if (write) { + char *data = iob->io_base; + for (; iob->io_resid != 0; iob->io_resid --) { + cputchar(*data ++); + } + return 0; + } + return -E_INVAL; +} + +static int +stdout_ioctl(struct device *dev, int op, void *data) { + return -E_INVAL; +} + +static void +stdout_device_init(struct device *dev) { + dev->d_blocks = 0; + dev->d_blocksize = 1; + dev->d_open = stdout_open; + dev->d_close = stdout_close; + dev->d_io = stdout_io; + dev->d_ioctl = stdout_ioctl; +} + +void +dev_init_stdout(void) { + struct inode *node; + if ((node = dev_create_inode()) == NULL) { + panic("stdout: dev_create_node.\n"); + } + stdout_device_init(vop_info(node, device)); + + int ret; + if ((ret = vfs_add_dev("stdout", node, 0)) != 0) { + panic("stdout: vfs_add_dev: %e.\n", ret); + } +} + diff --git a/code/lab8/kern/fs/file.c b/code/lab8/kern/fs/file.c new file mode 100644 index 0000000..df7a0a5 --- /dev/null +++ b/code/lab8/kern/fs/file.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define testfd(fd) ((fd) >= 0 && (fd) < FILES_STRUCT_NENTRY) + +// get_fd_array - get current process's open files table +static struct file * +get_fd_array(void) { + struct files_struct *filesp = current->filesp; + assert(filesp != NULL && files_count(filesp) > 0); + return filesp->fd_array; +} + +// fd_array_init - initialize the open files table +void +fd_array_init(struct file *fd_array) { + int fd; + struct file *file = fd_array; + for (fd = 0; fd < FILES_STRUCT_NENTRY; fd ++, file ++) { + atomic_set(&(file->open_count), 0); + file->status = FD_NONE, file->fd = fd; + } +} + +// fs_array_alloc - allocate a free file item (with FD_NONE status) in open files table +static int +fd_array_alloc(int fd, struct file **file_store) { +// panic("debug"); + struct file *file = get_fd_array(); + if (fd == NO_FD) { + for (fd = 0; fd < FILES_STRUCT_NENTRY; fd ++, file ++) { + if (file->status == FD_NONE) { + goto found; + } + } + return -E_MAX_OPEN; + } + else { + if (testfd(fd)) { + file += fd; + if (file->status == FD_NONE) { + goto found; + } + return -E_BUSY; + } + return -E_INVAL; + } +found: + assert(fopen_count(file) == 0); + file->status = FD_INIT, file->node = NULL; + *file_store = file; + return 0; +} + +// fd_array_free - free a file item in open files table +static void +fd_array_free(struct file *file) { + assert(file->status == FD_INIT || file->status == FD_CLOSED); + assert(fopen_count(file) == 0); + if (file->status == FD_CLOSED) { + vfs_close(file->node); + } + file->status = FD_NONE; +} + +static void +fd_array_acquire(struct file *file) { + assert(file->status == FD_OPENED); + fopen_count_inc(file); +} + +// fd_array_release - file's open_count--; if file's open_count-- == 0 , then call fd_array_free to free this file item +static void +fd_array_release(struct file *file) { + assert(file->status == FD_OPENED || file->status == FD_CLOSED); + assert(fopen_count(file) > 0); + if (fopen_count_dec(file) == 0) { + fd_array_free(file); + } +} + +// fd_array_open - file's open_count++, set status to FD_OPENED +void +fd_array_open(struct file *file) { + assert(file->status == FD_INIT && file->node != NULL); + file->status = FD_OPENED; + fopen_count_inc(file); +} + +// fd_array_close - file's open_count--; if file's open_count-- == 0 , then call fd_array_free to free this file item +void +fd_array_close(struct file *file) { + assert(file->status == FD_OPENED); + assert(fopen_count(file) > 0); + file->status = FD_CLOSED; + if (fopen_count_dec(file) == 0) { + fd_array_free(file); + } +} + +//fs_array_dup - duplicate file 'from' to file 'to' +void +fd_array_dup(struct file *to, struct file *from) { + //cprintf("[fd_array_dup]from fd=%d, to fd=%d\n",from->fd, to->fd); + assert(to->status == FD_INIT && from->status == FD_OPENED); + to->pos = from->pos; + to->readable = from->readable; + to->writable = from->writable; + struct inode *node = from->node; + vop_ref_inc(node), vop_open_inc(node); + to->node = node; + fd_array_open(to); +} + +// fd2file - use fd as index of fd_array, return the array item (file) +static inline int +fd2file(int fd, struct file **file_store) { + if (testfd(fd)) { + struct file *file = get_fd_array() + fd; + if (file->status == FD_OPENED && file->fd == fd) { + *file_store = file; + return 0; + } + } + return -E_INVAL; +} + +// file_testfd - test file is readble or writable? +bool +file_testfd(int fd, bool readable, bool writable) { + int ret; + struct file *file; + if ((ret = fd2file(fd, &file)) != 0) { + return 0; + } + if (readable && !file->readable) { + return 0; + } + if (writable && !file->writable) { + return 0; + } + return 1; +} + +// open file +int +file_open(char *path, uint32_t open_flags) { + bool readable = 0, writable = 0; + switch (open_flags & O_ACCMODE) { + case O_RDONLY: readable = 1; break; + case O_WRONLY: writable = 1; break; + case O_RDWR: + readable = writable = 1; + break; + default: + return -E_INVAL; + } + + int ret; + struct file *file; + if ((ret = fd_array_alloc(NO_FD, &file)) != 0) { + return ret; + } + + struct inode *node; + if ((ret = vfs_open(path, open_flags, &node)) != 0) { + fd_array_free(file); + return ret; + } + + file->pos = 0; + if (open_flags & O_APPEND) { + struct stat __stat, *stat = &__stat; + if ((ret = vop_fstat(node, stat)) != 0) { + vfs_close(node); + fd_array_free(file); + return ret; + } + file->pos = stat->st_size; + } + + file->node = node; + file->readable = readable; + file->writable = writable; + fd_array_open(file); + return file->fd; +} + +// close file +int +file_close(int fd) { + int ret; + struct file *file; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + fd_array_close(file); + return 0; +} + +// read file +int +file_read(int fd, void *base, size_t len, size_t *copied_store) { + int ret; + struct file *file; + *copied_store = 0; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + if (!file->readable) { + return -E_INVAL; + } + fd_array_acquire(file); + + struct iobuf __iob, *iob = iobuf_init(&__iob, base, len, file->pos); + ret = vop_read(file->node, iob); + + size_t copied = iobuf_used(iob); + if (file->status == FD_OPENED) { + file->pos += copied; + } + *copied_store = copied; + fd_array_release(file); + return ret; +} + +// write file +int +file_write(int fd, void *base, size_t len, size_t *copied_store) { + int ret; + struct file *file; + *copied_store = 0; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + if (!file->writable) { + return -E_INVAL; + } + fd_array_acquire(file); + + struct iobuf __iob, *iob = iobuf_init(&__iob, base, len, file->pos); + ret = vop_write(file->node, iob); + + size_t copied = iobuf_used(iob); + if (file->status == FD_OPENED) { + file->pos += copied; + } + *copied_store = copied; + fd_array_release(file); + return ret; +} + +// seek file +int +file_seek(int fd, off_t pos, int whence) { + struct stat __stat, *stat = &__stat; + int ret; + struct file *file; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + fd_array_acquire(file); + + switch (whence) { + case LSEEK_SET: break; + case LSEEK_CUR: pos += file->pos; break; + case LSEEK_END: + if ((ret = vop_fstat(file->node, stat)) == 0) { + pos += stat->st_size; + } + break; + default: ret = -E_INVAL; + } + + if (ret == 0) { + if ((ret = vop_tryseek(file->node, pos)) == 0) { + file->pos = pos; + } +// cprintf("file_seek, pos=%d, whence=%d, ret=%d\n", pos, whence, ret); + } + fd_array_release(file); + return ret; +} + +// stat file +int +file_fstat(int fd, struct stat *stat) { + int ret; + struct file *file; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + fd_array_acquire(file); + ret = vop_fstat(file->node, stat); + fd_array_release(file); + return ret; +} + +// sync file +int +file_fsync(int fd) { + int ret; + struct file *file; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + fd_array_acquire(file); + ret = vop_fsync(file->node); + fd_array_release(file); + return ret; +} + +// get file entry in DIR +int +file_getdirentry(int fd, struct dirent *direntp) { + int ret; + struct file *file; + if ((ret = fd2file(fd, &file)) != 0) { + return ret; + } + fd_array_acquire(file); + + struct iobuf __iob, *iob = iobuf_init(&__iob, direntp->name, sizeof(direntp->name), direntp->offset); + if ((ret = vop_getdirentry(file->node, iob)) == 0) { + direntp->offset += iobuf_used(iob); + } + fd_array_release(file); + return ret; +} + +// duplicate file +int +file_dup(int fd1, int fd2) { + int ret; + struct file *file1, *file2; + if ((ret = fd2file(fd1, &file1)) != 0) { + return ret; + } + if ((ret = fd_array_alloc(fd2, &file2)) != 0) { + return ret; + } + fd_array_dup(file2, file1); + return file2->fd; +} + + diff --git a/code/lab8/kern/fs/file.h b/code/lab8/kern/fs/file.h new file mode 100644 index 0000000..8b58ba8 --- /dev/null +++ b/code/lab8/kern/fs/file.h @@ -0,0 +1,60 @@ +#ifndef __KERN_FS_FILE_H__ +#define __KERN_FS_FILE_H__ + +//#include +#include +#include +#include +#include + +struct inode; +struct stat; +struct dirent; + +struct file { + enum { + FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED, + } status; + bool readable; + bool writable; + int fd; + off_t pos; + struct inode *node; + atomic_t open_count; +}; + +void fd_array_init(struct file *fd_array); +void fd_array_open(struct file *file); +void fd_array_close(struct file *file); +void fd_array_dup(struct file *to, struct file *from); +bool file_testfd(int fd, bool readable, bool writable); + +int file_open(char *path, uint32_t open_flags); +int file_close(int fd); +int file_read(int fd, void *base, size_t len, size_t *copied_store); +int file_write(int fd, void *base, size_t len, size_t *copied_store); +int file_seek(int fd, off_t pos, int whence); +int file_fstat(int fd, struct stat *stat); +int file_fsync(int fd); +int file_getdirentry(int fd, struct dirent *dirent); +int file_dup(int fd1, int fd2); +int file_pipe(int fd[]); +int file_mkfifo(const char *name, uint32_t open_flags); + +static inline int +fopen_count(struct file *file) { + return atomic_read(&(file->open_count)); +} + +static inline int +fopen_count_inc(struct file *file) { + return atomic_add_return(&(file->open_count), 1); +} + +static inline int +fopen_count_dec(struct file *file) { + return atomic_sub_return(&(file->open_count), 1); +} + +#endif /* !__KERN_FS_FILE_H__ */ + diff --git a/code/lab8/kern/fs/fs.c b/code/lab8/kern/fs/fs.c new file mode 100644 index 0000000..41f157c --- /dev/null +++ b/code/lab8/kern/fs/fs.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +//called when init_main proc start +void +fs_init(void) { + vfs_init(); + dev_init(); + sfs_init(); +} + +void +fs_cleanup(void) { + vfs_cleanup(); +} + +void +lock_files(struct files_struct *filesp) { + down(&(filesp->files_sem)); +} + +void +unlock_files(struct files_struct *filesp) { + up(&(filesp->files_sem)); +} +//Called when a new proc init +struct files_struct * +files_create(void) { + //cprintf("[files_create]\n"); + static_assert((int)FILES_STRUCT_NENTRY > 128); + struct files_struct *filesp; + if ((filesp = kmalloc(sizeof(struct files_struct) + FILES_STRUCT_BUFSIZE)) != NULL) { + filesp->pwd = NULL; + filesp->fd_array = (void *)(filesp + 1); + atomic_set(&(filesp->files_count), 0); + sem_init(&(filesp->files_sem), 1); + fd_array_init(filesp->fd_array); + } + return filesp; +} +//Called when a proc exit +void +files_destroy(struct files_struct *filesp) { +// cprintf("[files_destroy]\n"); + assert(filesp != NULL && files_count(filesp) == 0); + if (filesp->pwd != NULL) { + vop_ref_dec(filesp->pwd); + } + int i; + struct file *file = filesp->fd_array; + for (i = 0; i < FILES_STRUCT_NENTRY; i ++, file ++) { + if (file->status == FD_OPENED) { + fd_array_close(file); + } + assert(file->status == FD_NONE); + } + kfree(filesp); +} + +void +files_closeall(struct files_struct *filesp) { +// cprintf("[files_closeall]\n"); + assert(filesp != NULL && files_count(filesp) > 0); + int i; + struct file *file = filesp->fd_array; + //skip the stdin & stdout + for (i = 2, file += 2; i < FILES_STRUCT_NENTRY; i ++, file ++) { + if (file->status == FD_OPENED) { + fd_array_close(file); + } + } +} + +int +dup_fs(struct files_struct *to, struct files_struct *from) { +// cprintf("[dup_fs]\n"); + assert(to != NULL && from != NULL); + assert(files_count(to) == 0 && files_count(from) > 0); + if ((to->pwd = from->pwd) != NULL) { + vop_ref_inc(to->pwd); + } + int i; + struct file *to_file = to->fd_array, *from_file = from->fd_array; + for (i = 0; i < FILES_STRUCT_NENTRY; i ++, to_file ++, from_file ++) { + if (from_file->status == FD_OPENED) { + /* alloc_fd first */ + to_file->status = FD_INIT; + fd_array_dup(to_file, from_file); + } + } + return 0; +} + diff --git a/code/lab8/kern/fs/fs.h b/code/lab8/kern/fs/fs.h new file mode 100644 index 0000000..f9acb36 --- /dev/null +++ b/code/lab8/kern/fs/fs.h @@ -0,0 +1,59 @@ +#ifndef __KERN_FS_FS_H__ +#define __KERN_FS_FS_H__ + +#include +#include +#include +#include + +#define SECTSIZE 512 +#define PAGE_NSECT (PGSIZE / SECTSIZE) + +#define SWAP_DEV_NO 1 +#define DISK0_DEV_NO 2 +#define DISK1_DEV_NO 3 + +void fs_init(void); +void fs_cleanup(void); + +struct inode; +struct file; + +/* + * process's file related informaction + */ +struct files_struct { + struct inode *pwd; // inode of present working directory + struct file *fd_array; // opened files array + atomic_t files_count; // the number of opened files + semaphore_t files_sem; // lock protect sem +}; + +#define FILES_STRUCT_BUFSIZE (PGSIZE - sizeof(struct files_struct)) +#define FILES_STRUCT_NENTRY (FILES_STRUCT_BUFSIZE / sizeof(struct file)) + +void lock_files(struct files_struct *filesp); +void unlock_files(struct files_struct *filesp); + +struct files_struct *files_create(void); +void files_destroy(struct files_struct *filesp); +void files_closeall(struct files_struct *filesp); +int dup_files(struct files_struct *to, struct files_struct *from); + +static inline int +files_count(struct files_struct *filesp) { + return atomic_read(&(filesp->files_count)); +} + +static inline int +files_count_inc(struct files_struct *filesp) { + return atomic_add_return(&(filesp->files_count), 1); +} + +static inline int +files_count_dec(struct files_struct *filesp) { + return atomic_sub_return(&(filesp->files_count), 1); +} + +#endif /* !__KERN_FS_FS_H__ */ + diff --git a/code/lab8/kern/fs/iobuf.c b/code/lab8/kern/fs/iobuf.c new file mode 100644 index 0000000..2d13b2e --- /dev/null +++ b/code/lab8/kern/fs/iobuf.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +/* + * iobuf_init - init io buffer struct. + * set up io_base to point to the buffer you want to transfer to, and set io_len to the length of buffer; + * initialize io_offset as desired; + * initialize io_resid to the total amount of data that can be transferred through this io. + */ +struct iobuf * +iobuf_init(struct iobuf *iob, void *base, size_t len, off_t offset) { + iob->io_base = base; + iob->io_offset = offset; + iob->io_len = iob->io_resid = len; + return iob; +} + +/* iobuf_move - move data (iob->io_base ---> data OR data --> iob->io.base) in memory + * @copiedp: the size of data memcopied + * + * iobuf_move may be called repeatedly on the same io to transfer + * additional data until the available buffer space the io refers to + * is exhausted. + */ +int +iobuf_move(struct iobuf *iob, void *data, size_t len, bool m2b, size_t *copiedp) { + size_t alen; + if ((alen = iob->io_resid) > len) { + alen = len; + } + if (alen > 0) { + void *src = iob->io_base, *dst = data; + if (m2b) { + void *tmp = src; + src = dst, dst = tmp; + } + memmove(dst, src, alen); + iobuf_skip(iob, alen), len -= alen; + } + if (copiedp != NULL) { + *copiedp = alen; + } + return (len == 0) ? 0 : -E_NO_MEM; +} + +/* + * iobuf_move_zeros - set io buffer zero + * @copiedp: the size of data memcopied + */ +int +iobuf_move_zeros(struct iobuf *iob, size_t len, size_t *copiedp) { + size_t alen; + if ((alen = iob->io_resid) > len) { + alen = len; + } + if (alen > 0) { + memset(iob->io_base, 0, alen); + iobuf_skip(iob, alen), len -= alen; + } + if (copiedp != NULL) { + *copiedp = alen; + } + return (len == 0) ? 0 : -E_NO_MEM; +} + +/* + * iobuf_skip - change the current position of io buffer + */ +void +iobuf_skip(struct iobuf *iob, size_t n) { + assert(iob->io_resid >= n); + iob->io_base += n, iob->io_offset += n, iob->io_resid -= n; +} + diff --git a/code/lab8/kern/fs/iobuf.h b/code/lab8/kern/fs/iobuf.h new file mode 100644 index 0000000..8bb668a --- /dev/null +++ b/code/lab8/kern/fs/iobuf.h @@ -0,0 +1,24 @@ +#ifndef __KERN_FS_IOBUF_H__ +#define __KERN_FS_IOBUF_H__ + +#include + +/* + * iobuf is a buffer Rd/Wr status record + */ +struct iobuf { + void *io_base; // the base addr of buffer (used for Rd/Wr) + off_t io_offset; // current Rd/Wr position in buffer, will have been incremented by the amount transferred + size_t io_len; // the length of buffer (used for Rd/Wr) + size_t io_resid; // current resident length need to Rd/Wr, will have been decremented by the amount transferred. +}; + +#define iobuf_used(iob) ((size_t)((iob)->io_len - (iob)->io_resid)) + +struct iobuf *iobuf_init(struct iobuf *iob, void *base, size_t len, off_t offset); +int iobuf_move(struct iobuf *iob, void *data, size_t len, bool m2b, size_t *copiedp); +int iobuf_move_zeros(struct iobuf *iob, size_t len, size_t *copiedp); +void iobuf_skip(struct iobuf *iob, size_t n); + +#endif /* !__KERN_FS_IOBUF_H__ */ + diff --git a/code/lab8/kern/fs/sfs/bitmap.c b/code/lab8/kern/fs/sfs/bitmap.c new file mode 100644 index 0000000..c2d1c8d --- /dev/null +++ b/code/lab8/kern/fs/sfs/bitmap.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include + +#define WORD_TYPE uint32_t +#define WORD_BITS (sizeof(WORD_TYPE) * CHAR_BIT) + +struct bitmap { + uint32_t nbits; + uint32_t nwords; + WORD_TYPE *map; +}; + +// bitmap_create - allocate a new bitmap object. +struct bitmap * +bitmap_create(uint32_t nbits) { + static_assert(WORD_BITS != 0); + assert(nbits != 0 && nbits + WORD_BITS > nbits); + + struct bitmap *bitmap; + if ((bitmap = kmalloc(sizeof(struct bitmap))) == NULL) { + return NULL; + } + + uint32_t nwords = ROUNDUP_DIV(nbits, WORD_BITS); + WORD_TYPE *map; + if ((map = kmalloc(sizeof(WORD_TYPE) * nwords)) == NULL) { + kfree(bitmap); + return NULL; + } + + bitmap->nbits = nbits, bitmap->nwords = nwords; + bitmap->map = memset(map, 0xFF, sizeof(WORD_TYPE) * nwords); + + /* mark any leftover bits at the end in use(0) */ + if (nbits != nwords * WORD_BITS) { + uint32_t ix = nwords - 1, overbits = nbits - ix * WORD_BITS; + + assert(nbits / WORD_BITS == ix); + assert(overbits > 0 && overbits < WORD_BITS); + + for (; overbits < WORD_BITS; overbits ++) { + bitmap->map[ix] ^= (1 << overbits); + } + } + return bitmap; +} + +// bitmap_alloc - locate a cleared bit, set it, and return its index. +int +bitmap_alloc(struct bitmap *bitmap, uint32_t *index_store) { + WORD_TYPE *map = bitmap->map; + uint32_t ix, offset, nwords = bitmap->nwords; + for (ix = 0; ix < nwords; ix ++) { + if (map[ix] != 0) { + for (offset = 0; offset < WORD_BITS; offset ++) { + WORD_TYPE mask = (1 << offset); + if (map[ix] & mask) { + map[ix] ^= mask; + *index_store = ix * WORD_BITS + offset; + return 0; + } + } + assert(0); + } + } + return -E_NO_MEM; +} + +// bitmap_translate - according index, get the related word and mask +static void +bitmap_translate(struct bitmap *bitmap, uint32_t index, WORD_TYPE **word, WORD_TYPE *mask) { + assert(index < bitmap->nbits); + uint32_t ix = index / WORD_BITS, offset = index % WORD_BITS; + *word = bitmap->map + ix; + *mask = (1 << offset); +} + +// bitmap_test - according index, get the related value (0 OR 1) in the bitmap +bool +bitmap_test(struct bitmap *bitmap, uint32_t index) { + WORD_TYPE *word, mask; + bitmap_translate(bitmap, index, &word, &mask); + return (*word & mask); +} + +// bitmap_free - according index, set related bit to 1 +void +bitmap_free(struct bitmap *bitmap, uint32_t index) { + WORD_TYPE *word, mask; + bitmap_translate(bitmap, index, &word, &mask); + assert(!(*word & mask)); + *word |= mask; +} + +// bitmap_destroy - free memory contains bitmap +void +bitmap_destroy(struct bitmap *bitmap) { + kfree(bitmap->map); + kfree(bitmap); +} + +// bitmap_getdata - return bitmap->map, return the length of bits to len_store +void * +bitmap_getdata(struct bitmap *bitmap, size_t *len_store) { + if (len_store != NULL) { + *len_store = sizeof(WORD_TYPE) * bitmap->nwords; + } + return bitmap->map; +} + diff --git a/code/lab8/kern/fs/sfs/bitmap.h b/code/lab8/kern/fs/sfs/bitmap.h new file mode 100644 index 0000000..4a3a8eb --- /dev/null +++ b/code/lab8/kern/fs/sfs/bitmap.h @@ -0,0 +1,32 @@ +#ifndef __KERN_FS_SFS_BITMAP_H__ +#define __KERN_FS_SFS_BITMAP_H__ + +#include + + +/* + * Fixed-size array of bits. (Intended for storage management.) + * + * Functions: + * bitmap_create - allocate a new bitmap object. + * Returns NULL on error. + * bitmap_getdata - return pointer to raw bit data (for I/O). + * bitmap_alloc - locate a cleared bit, set it, and return its index. + * bitmap_mark - set a clear bit by its index. + * bitmap_unmark - clear a set bit by its index. + * bitmap_isset - return whether a particular bit is set or not. + * bitmap_destroy - destroy bitmap. + */ + + +struct bitmap; + +struct bitmap *bitmap_create(uint32_t nbits); // allocate a new bitmap object. +int bitmap_alloc(struct bitmap *bitmap, uint32_t *index_store); // locate a cleared bit, set it, and return its index. +bool bitmap_test(struct bitmap *bitmap, uint32_t index); // return whether a particular bit is set or not. +void bitmap_free(struct bitmap *bitmap, uint32_t index); // according index, set related bit to 1 +void bitmap_destroy(struct bitmap *bitmap); // free memory contains bitmap +void *bitmap_getdata(struct bitmap *bitmap, size_t *len_store); // return pointer to raw bit data (for I/O) + +#endif /* !__KERN_FS_SFS_BITMAP_H__ */ + diff --git a/code/lab8/kern/fs/sfs/sfs.c b/code/lab8/kern/fs/sfs/sfs.c new file mode 100644 index 0000000..c1d7d44 --- /dev/null +++ b/code/lab8/kern/fs/sfs/sfs.c @@ -0,0 +1,19 @@ +#include +#include +#include +#include + +/* + * sfs_init - mount sfs on disk0 + * + * CALL GRAPH: + * kern_init-->fs_init-->sfs_init + */ +void +sfs_init(void) { + int ret; + if ((ret = sfs_mount("disk0")) != 0) { + panic("failed: sfs: sfs_mount: %e.\n", ret); + } +} + diff --git a/code/lab8/kern/fs/sfs/sfs.h b/code/lab8/kern/fs/sfs/sfs.h new file mode 100644 index 0000000..39c1772 --- /dev/null +++ b/code/lab8/kern/fs/sfs/sfs.h @@ -0,0 +1,129 @@ +#ifndef __KERN_FS_SFS_SFS_H__ +#define __KERN_FS_SFS_SFS_H__ + +#include +#include +#include +#include +#include + +/* + * Simple FS (SFS) definitions visible to ucore. This covers the on-disk format + * and is used by tools that work on SFS volumes, such as mksfs. + */ + +#define SFS_MAGIC 0x2f8dbe2a /* magic number for sfs */ +#define SFS_BLKSIZE PGSIZE /* size of block */ +#define SFS_NDIRECT 12 /* # of direct blocks in inode */ +#define SFS_MAX_INFO_LEN 31 /* max length of infomation */ +#define SFS_MAX_FNAME_LEN FS_MAX_FNAME_LEN /* max length of filename */ +#define SFS_MAX_FILE_SIZE (1024UL * 1024 * 128) /* max file size (128M) */ +#define SFS_BLKN_SUPER 0 /* block the superblock lives in */ +#define SFS_BLKN_ROOT 1 /* location of the root dir inode */ +#define SFS_BLKN_FREEMAP 2 /* 1st block of the freemap */ + +/* # of bits in a block */ +#define SFS_BLKBITS (SFS_BLKSIZE * CHAR_BIT) + +/* # of entries in a block */ +#define SFS_BLK_NENTRY (SFS_BLKSIZE / sizeof(uint32_t)) + +/* file types */ +#define SFS_TYPE_INVAL 0 /* Should not appear on disk */ +#define SFS_TYPE_FILE 1 +#define SFS_TYPE_DIR 2 +#define SFS_TYPE_LINK 3 + +/* + * On-disk superblock + */ +struct sfs_super { + uint32_t magic; /* magic number, should be SFS_MAGIC */ + uint32_t blocks; /* # of blocks in fs */ + uint32_t unused_blocks; /* # of unused blocks in fs */ + char info[SFS_MAX_INFO_LEN + 1]; /* infomation for sfs */ +}; + +/* inode (on disk) */ +struct sfs_disk_inode { + uint32_t size; /* size of the file (in bytes) */ + uint16_t type; /* one of SYS_TYPE_* above */ + uint16_t nlinks; /* # of hard links to this file */ + uint32_t blocks; /* # of blocks */ + uint32_t direct[SFS_NDIRECT]; /* direct blocks */ + uint32_t indirect; /* indirect blocks */ +// uint32_t db_indirect; /* double indirect blocks */ +// unused +}; + +/* file entry (on disk) */ +struct sfs_disk_entry { + uint32_t ino; /* inode number */ + char name[SFS_MAX_FNAME_LEN + 1]; /* file name */ +}; + +#define sfs_dentry_size \ + sizeof(((struct sfs_disk_entry *)0)->name) + +/* inode for sfs */ +struct sfs_inode { + struct sfs_disk_inode *din; /* on-disk inode */ + uint32_t ino; /* inode number */ + bool dirty; /* true if inode modified */ + int reclaim_count; /* kill inode if it hits zero */ + semaphore_t sem; /* semaphore for din */ + list_entry_t inode_link; /* entry for linked-list in sfs_fs */ + list_entry_t hash_link; /* entry for hash linked-list in sfs_fs */ +}; + +#define le2sin(le, member) \ + to_struct((le), struct sfs_inode, member) + +/* filesystem for sfs */ +struct sfs_fs { + struct sfs_super super; /* on-disk superblock */ + struct device *dev; /* device mounted on */ + struct bitmap *freemap; /* blocks in use are mared 0 */ + bool super_dirty; /* true if super/freemap modified */ + void *sfs_buffer; /* buffer for non-block aligned io */ + semaphore_t fs_sem; /* semaphore for fs */ + semaphore_t io_sem; /* semaphore for io */ + semaphore_t mutex_sem; /* semaphore for link/unlink and rename */ + list_entry_t inode_list; /* inode linked-list */ + list_entry_t *hash_list; /* inode hash linked-list */ +}; + +/* hash for sfs */ +#define SFS_HLIST_SHIFT 10 +#define SFS_HLIST_SIZE (1 << SFS_HLIST_SHIFT) +#define sin_hashfn(x) (hash32(x, SFS_HLIST_SHIFT)) + +/* size of freemap (in bits) */ +#define sfs_freemap_bits(super) ROUNDUP((super)->blocks, SFS_BLKBITS) + +/* size of freemap (in blocks) */ +#define sfs_freemap_blocks(super) ROUNDUP_DIV((super)->blocks, SFS_BLKBITS) + +struct fs; +struct inode; + +void sfs_init(void); +int sfs_mount(const char *devname); + +void lock_sfs_fs(struct sfs_fs *sfs); +void lock_sfs_io(struct sfs_fs *sfs); +void unlock_sfs_fs(struct sfs_fs *sfs); +void unlock_sfs_io(struct sfs_fs *sfs); + +int sfs_rblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks); +int sfs_wblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks); +int sfs_rbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset); +int sfs_wbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset); +int sfs_sync_super(struct sfs_fs *sfs); +int sfs_sync_freemap(struct sfs_fs *sfs); +int sfs_clear_block(struct sfs_fs *sfs, uint32_t blkno, uint32_t nblks); + +int sfs_load_inode(struct sfs_fs *sfs, struct inode **node_store, uint32_t ino); + +#endif /* !__KERN_FS_SFS_SFS_H__ */ + diff --git a/code/lab8/kern/fs/sfs/sfs_fs.c b/code/lab8/kern/fs/sfs/sfs_fs.c new file mode 100644 index 0000000..6f4dc19 --- /dev/null +++ b/code/lab8/kern/fs/sfs/sfs_fs.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * sfs_sync - sync sfs's superblock and freemap in memroy into disk + */ +static int +sfs_sync(struct fs *fs) { + struct sfs_fs *sfs = fsop_info(fs, sfs); + lock_sfs_fs(sfs); + { + list_entry_t *list = &(sfs->inode_list), *le = list; + while ((le = list_next(le)) != list) { + struct sfs_inode *sin = le2sin(le, inode_link); + vop_fsync(info2node(sin, sfs_inode)); + } + } + unlock_sfs_fs(sfs); + + int ret; + if (sfs->super_dirty) { + sfs->super_dirty = 0; + if ((ret = sfs_sync_super(sfs)) != 0) { + sfs->super_dirty = 1; + return ret; + } + if ((ret = sfs_sync_freemap(sfs)) != 0) { + sfs->super_dirty = 1; + return ret; + } + } + return 0; +} + +/* + * sfs_get_root - get the root directory inode from disk (SFS_BLKN_ROOT,1) + */ +static struct inode * +sfs_get_root(struct fs *fs) { + struct inode *node; + int ret; + if ((ret = sfs_load_inode(fsop_info(fs, sfs), &node, SFS_BLKN_ROOT)) != 0) { + panic("load sfs root failed: %e", ret); + } + return node; +} + +/* + * sfs_unmount - unmount sfs, and free the memorys contain sfs->freemap/sfs_buffer/hash_liskt and sfs itself. + */ +static int +sfs_unmount(struct fs *fs) { + struct sfs_fs *sfs = fsop_info(fs, sfs); + if (!list_empty(&(sfs->inode_list))) { + return -E_BUSY; + } + assert(!sfs->super_dirty); + bitmap_destroy(sfs->freemap); + kfree(sfs->sfs_buffer); + kfree(sfs->hash_list); + kfree(sfs); + return 0; +} + +/* + * sfs_cleanup - when sfs failed, then should call this function to sync sfs by calling sfs_sync + * + * NOTICE: nouse now. + */ +static void +sfs_cleanup(struct fs *fs) { + struct sfs_fs *sfs = fsop_info(fs, sfs); + uint32_t blocks = sfs->super.blocks, unused_blocks = sfs->super.unused_blocks; + cprintf("sfs: cleanup: '%s' (%d/%d/%d)\n", sfs->super.info, + blocks - unused_blocks, unused_blocks, blocks); + int i, ret; + for (i = 0; i < 32; i ++) { + if ((ret = fsop_sync(fs)) == 0) { + break; + } + } + if (ret != 0) { + warn("sfs: sync error: '%s': %e.\n", sfs->super.info, ret); + } +} + +/* + * sfs_init_read - used in sfs_do_mount to read disk block(blkno, 1) directly. + * + * @dev: the block device + * @blkno: the NO. of disk block + * @blk_buffer: the buffer used for read + * + * (1) init iobuf + * (2) read dev into iobuf + */ +static int +sfs_init_read(struct device *dev, uint32_t blkno, void *blk_buffer) { + struct iobuf __iob, *iob = iobuf_init(&__iob, blk_buffer, SFS_BLKSIZE, blkno * SFS_BLKSIZE); + return dop_io(dev, iob, 0); +} + +/* + * sfs_init_freemap - used in sfs_do_mount to read freemap data info in disk block(blkno, nblks) directly. + * + * @dev: the block device + * @bitmap: the bitmap in memroy + * @blkno: the NO. of disk block + * @nblks: Rd number of disk block + * @blk_buffer: the buffer used for read + * + * (1) get data addr in bitmap + * (2) read dev into iobuf + */ +static int +sfs_init_freemap(struct device *dev, struct bitmap *freemap, uint32_t blkno, uint32_t nblks, void *blk_buffer) { + size_t len; + void *data = bitmap_getdata(freemap, &len); + assert(data != NULL && len == nblks * SFS_BLKSIZE); + while (nblks != 0) { + int ret; + if ((ret = sfs_init_read(dev, blkno, data)) != 0) { + return ret; + } + blkno ++, nblks --, data += SFS_BLKSIZE; + } + return 0; +} + +/* + * sfs_do_mount - mount sfs file system. + * + * @dev: the block device contains sfs file system + * @fs_store: the fs struct in memroy + */ +static int +sfs_do_mount(struct device *dev, struct fs **fs_store) { + static_assert(SFS_BLKSIZE >= sizeof(struct sfs_super)); + static_assert(SFS_BLKSIZE >= sizeof(struct sfs_disk_inode)); + static_assert(SFS_BLKSIZE >= sizeof(struct sfs_disk_entry)); + + if (dev->d_blocksize != SFS_BLKSIZE) { + return -E_NA_DEV; + } + + /* allocate fs structure */ + struct fs *fs; + if ((fs = alloc_fs(sfs)) == NULL) { + return -E_NO_MEM; + } + struct sfs_fs *sfs = fsop_info(fs, sfs); + sfs->dev = dev; + + int ret = -E_NO_MEM; + + void *sfs_buffer; + if ((sfs->sfs_buffer = sfs_buffer = kmalloc(SFS_BLKSIZE)) == NULL) { + goto failed_cleanup_fs; + } + + /* load and check superblock */ + if ((ret = sfs_init_read(dev, SFS_BLKN_SUPER, sfs_buffer)) != 0) { + goto failed_cleanup_sfs_buffer; + } + + ret = -E_INVAL; + + struct sfs_super *super = sfs_buffer; + if (super->magic != SFS_MAGIC) { + cprintf("sfs: wrong magic in superblock. (%08x should be %08x).\n", + super->magic, SFS_MAGIC); + goto failed_cleanup_sfs_buffer; + } + if (super->blocks > dev->d_blocks) { + cprintf("sfs: fs has %u blocks, device has %u blocks.\n", + super->blocks, dev->d_blocks); + goto failed_cleanup_sfs_buffer; + } + super->info[SFS_MAX_INFO_LEN] = '\0'; + sfs->super = *super; + + ret = -E_NO_MEM; + + uint32_t i; + + /* alloc and initialize hash list */ + list_entry_t *hash_list; + if ((sfs->hash_list = hash_list = kmalloc(sizeof(list_entry_t) * SFS_HLIST_SIZE)) == NULL) { + goto failed_cleanup_sfs_buffer; + } + for (i = 0; i < SFS_HLIST_SIZE; i ++) { + list_init(hash_list + i); + } + + /* load and check freemap */ + struct bitmap *freemap; + uint32_t freemap_size_nbits = sfs_freemap_bits(super); + if ((sfs->freemap = freemap = bitmap_create(freemap_size_nbits)) == NULL) { + goto failed_cleanup_hash_list; + } + uint32_t freemap_size_nblks = sfs_freemap_blocks(super); + if ((ret = sfs_init_freemap(dev, freemap, SFS_BLKN_FREEMAP, freemap_size_nblks, sfs_buffer)) != 0) { + goto failed_cleanup_freemap; + } + + uint32_t blocks = sfs->super.blocks, unused_blocks = 0; + for (i = 0; i < freemap_size_nbits; i ++) { + if (bitmap_test(freemap, i)) { + unused_blocks ++; + } + } + assert(unused_blocks == sfs->super.unused_blocks); + + /* and other fields */ + sfs->super_dirty = 0; + sem_init(&(sfs->fs_sem), 1); + sem_init(&(sfs->io_sem), 1); + sem_init(&(sfs->mutex_sem), 1); + list_init(&(sfs->inode_list)); + cprintf("sfs: mount: '%s' (%d/%d/%d)\n", sfs->super.info, + blocks - unused_blocks, unused_blocks, blocks); + + /* link addr of sync/get_root/unmount/cleanup funciton fs's function pointers*/ + fs->fs_sync = sfs_sync; + fs->fs_get_root = sfs_get_root; + fs->fs_unmount = sfs_unmount; + fs->fs_cleanup = sfs_cleanup; + *fs_store = fs; + return 0; + +failed_cleanup_freemap: + bitmap_destroy(freemap); +failed_cleanup_hash_list: + kfree(hash_list); +failed_cleanup_sfs_buffer: + kfree(sfs_buffer); +failed_cleanup_fs: + kfree(fs); + return ret; +} + +int +sfs_mount(const char *devname) { + return vfs_mount(devname, sfs_do_mount); +} + diff --git a/code/lab8/kern/fs/sfs/sfs_inode.c b/code/lab8/kern/fs/sfs/sfs_inode.c new file mode 100644 index 0000000..0c4108e --- /dev/null +++ b/code/lab8/kern/fs/sfs/sfs_inode.c @@ -0,0 +1,987 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct inode_ops sfs_node_dirops; // dir operations +static const struct inode_ops sfs_node_fileops; // file operations + +/* + * lock_sin - lock the process of inode Rd/Wr + */ +static void +lock_sin(struct sfs_inode *sin) { + down(&(sin->sem)); +} + +/* + * unlock_sin - unlock the process of inode Rd/Wr + */ +static void +unlock_sin(struct sfs_inode *sin) { + up(&(sin->sem)); +} + +/* + * sfs_get_ops - return function addr of fs_node_dirops/sfs_node_fileops + */ +static const struct inode_ops * +sfs_get_ops(uint16_t type) { + switch (type) { + case SFS_TYPE_DIR: + return &sfs_node_dirops; + case SFS_TYPE_FILE: + return &sfs_node_fileops; + } + panic("invalid file type %d.\n", type); +} + +/* + * sfs_hash_list - return inode entry in sfs->hash_list + */ +static list_entry_t * +sfs_hash_list(struct sfs_fs *sfs, uint32_t ino) { + return sfs->hash_list + sin_hashfn(ino); +} + +/* + * sfs_set_links - link inode sin in sfs->linked-list AND sfs->hash_link + */ +static void +sfs_set_links(struct sfs_fs *sfs, struct sfs_inode *sin) { + list_add(&(sfs->inode_list), &(sin->inode_link)); + list_add(sfs_hash_list(sfs, sin->ino), &(sin->hash_link)); +} + +/* + * sfs_remove_links - unlink inode sin in sfs->linked-list AND sfs->hash_link + */ +static void +sfs_remove_links(struct sfs_inode *sin) { + list_del(&(sin->inode_link)); + list_del(&(sin->hash_link)); +} + +/* + * sfs_block_inuse - check the inode with NO. ino inuse info in bitmap + */ +static bool +sfs_block_inuse(struct sfs_fs *sfs, uint32_t ino) { + if (ino != 0 && ino < sfs->super.blocks) { + return !bitmap_test(sfs->freemap, ino); + } + panic("sfs_block_inuse: called out of range (0, %u) %u.\n", sfs->super.blocks, ino); +} + +/* + * sfs_block_alloc - check and get a free disk block + */ +static int +sfs_block_alloc(struct sfs_fs *sfs, uint32_t *ino_store) { + int ret; + if ((ret = bitmap_alloc(sfs->freemap, ino_store)) != 0) { + return ret; + } + assert(sfs->super.unused_blocks > 0); + sfs->super.unused_blocks --, sfs->super_dirty = 1; + assert(sfs_block_inuse(sfs, *ino_store)); + return sfs_clear_block(sfs, *ino_store, 1); +} + +/* + * sfs_block_free - set related bits for ino block to 1(means free) in bitmap, add sfs->super.unused_blocks, set superblock dirty * + */ +static void +sfs_block_free(struct sfs_fs *sfs, uint32_t ino) { + assert(sfs_block_inuse(sfs, ino)); + bitmap_free(sfs->freemap, ino); + sfs->super.unused_blocks ++, sfs->super_dirty = 1; +} + +/* + * sfs_create_inode - alloc a inode in memroy, and init din/ino/dirty/reclian_count/sem fields in sfs_inode in inode + */ +static int +sfs_create_inode(struct sfs_fs *sfs, struct sfs_disk_inode *din, uint32_t ino, struct inode **node_store) { + struct inode *node; + if ((node = alloc_inode(sfs_inode)) != NULL) { + vop_init(node, sfs_get_ops(din->type), info2fs(sfs, sfs)); + struct sfs_inode *sin = vop_info(node, sfs_inode); + sin->din = din, sin->ino = ino, sin->dirty = 0, sin->reclaim_count = 1; + sem_init(&(sin->sem), 1); + *node_store = node; + return 0; + } + return -E_NO_MEM; +} + +/* + * lookup_sfs_nolock - according ino, find related inode + * + * NOTICE: le2sin, info2node MACRO + */ +static struct inode * +lookup_sfs_nolock(struct sfs_fs *sfs, uint32_t ino) { + struct inode *node; + list_entry_t *list = sfs_hash_list(sfs, ino), *le = list; + while ((le = list_next(le)) != list) { + struct sfs_inode *sin = le2sin(le, hash_link); + if (sin->ino == ino) { + node = info2node(sin, sfs_inode); + if (vop_ref_inc(node) == 1) { + sin->reclaim_count ++; + } + return node; + } + } + return NULL; +} + +/* + * sfs_load_inode - If the inode isn't existed, load inode related ino disk block data into a new created inode. + * If the inode is in memory alreadily, then do nothing + */ +int +sfs_load_inode(struct sfs_fs *sfs, struct inode **node_store, uint32_t ino) { + lock_sfs_fs(sfs); + struct inode *node; + if ((node = lookup_sfs_nolock(sfs, ino)) != NULL) { + goto out_unlock; + } + + int ret = -E_NO_MEM; + struct sfs_disk_inode *din; + if ((din = kmalloc(sizeof(struct sfs_disk_inode))) == NULL) { + goto failed_unlock; + } + + assert(sfs_block_inuse(sfs, ino)); + if ((ret = sfs_rbuf(sfs, din, sizeof(struct sfs_disk_inode), ino, 0)) != 0) { + goto failed_cleanup_din; + } + + assert(din->nlinks != 0); + if ((ret = sfs_create_inode(sfs, din, ino, &node)) != 0) { + goto failed_cleanup_din; + } + sfs_set_links(sfs, vop_info(node, sfs_inode)); + +out_unlock: + unlock_sfs_fs(sfs); + *node_store = node; + return 0; + +failed_cleanup_din: + kfree(din); +failed_unlock: + unlock_sfs_fs(sfs); + return ret; +} + +/* + * sfs_bmap_get_sub_nolock - according entry pointer entp and index, find the index of indrect disk block + * return the index of indrect disk block to ino_store. no lock protect + * @sfs: sfs file system + * @entp: the pointer of index of entry disk block + * @index: the index of block in indrect block + * @create: BOOL, if the block isn't allocated, if create = 1 the alloc a block, otherwise just do nothing + * @ino_store: 0 OR the index of already inused block or new allocated block. + */ +static int +sfs_bmap_get_sub_nolock(struct sfs_fs *sfs, uint32_t *entp, uint32_t index, bool create, uint32_t *ino_store) { + assert(index < SFS_BLK_NENTRY); + int ret; + uint32_t ent, ino = 0; + off_t offset = index * sizeof(uint32_t); // the offset of entry in entry block + // if entry block is existd, read the content of entry block into sfs->sfs_buffer + if ((ent = *entp) != 0) { + if ((ret = sfs_rbuf(sfs, &ino, sizeof(uint32_t), ent, offset)) != 0) { + return ret; + } + if (ino != 0 || !create) { + goto out; + } + } + else { + if (!create) { + goto out; + } + //if entry block isn't existd, allocated a entry block (for indrect block) + if ((ret = sfs_block_alloc(sfs, &ent)) != 0) { + return ret; + } + } + + if ((ret = sfs_block_alloc(sfs, &ino)) != 0) { + goto failed_cleanup; + } + if ((ret = sfs_wbuf(sfs, &ino, sizeof(uint32_t), ent, offset)) != 0) { + sfs_block_free(sfs, ino); + goto failed_cleanup; + } + +out: + if (ent != *entp) { + *entp = ent; + } + *ino_store = ino; + return 0; + +failed_cleanup: + if (ent != *entp) { + sfs_block_free(sfs, ent); + } + return ret; +} + +/* + * sfs_bmap_get_nolock - according sfs_inode and index of block, find the NO. of disk block + * no lock protect + * @sfs: sfs file system + * @sin: sfs inode in memory + * @index: the index of block in inode + * @create: BOOL, if the block isn't allocated, if create = 1 the alloc a block, otherwise just do nothing + * @ino_store: 0 OR the index of already inused block or new allocated block. + */ +static int +sfs_bmap_get_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, uint32_t index, bool create, uint32_t *ino_store) { + struct sfs_disk_inode *din = sin->din; + int ret; + uint32_t ent, ino; + // the index of disk block is in the fist SFS_NDIRECT direct blocks + if (index < SFS_NDIRECT) { + if ((ino = din->direct[index]) == 0 && create) { + if ((ret = sfs_block_alloc(sfs, &ino)) != 0) { + return ret; + } + din->direct[index] = ino; + sin->dirty = 1; + } + goto out; + } + // the index of disk block is in the indirect blocks. + index -= SFS_NDIRECT; + if (index < SFS_BLK_NENTRY) { + ent = din->indirect; + if ((ret = sfs_bmap_get_sub_nolock(sfs, &ent, index, create, &ino)) != 0) { + return ret; + } + if (ent != din->indirect) { + assert(din->indirect == 0); + din->indirect = ent; + sin->dirty = 1; + } + goto out; + } else { + panic ("sfs_bmap_get_nolock - index out of range"); + } +out: + assert(ino == 0 || sfs_block_inuse(sfs, ino)); + *ino_store = ino; + return 0; +} + +/* + * sfs_bmap_free_sub_nolock - set the entry item to 0 (free) in the indirect block + */ +static int +sfs_bmap_free_sub_nolock(struct sfs_fs *sfs, uint32_t ent, uint32_t index) { + assert(sfs_block_inuse(sfs, ent) && index < SFS_BLK_NENTRY); + int ret; + uint32_t ino, zero = 0; + off_t offset = index * sizeof(uint32_t); + if ((ret = sfs_rbuf(sfs, &ino, sizeof(uint32_t), ent, offset)) != 0) { + return ret; + } + if (ino != 0) { + if ((ret = sfs_wbuf(sfs, &zero, sizeof(uint32_t), ent, offset)) != 0) { + return ret; + } + sfs_block_free(sfs, ino); + } + return 0; +} + +/* + * sfs_bmap_free_nolock - free a block with logical index in inode and reset the inode's fields + */ +static int +sfs_bmap_free_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, uint32_t index) { + struct sfs_disk_inode *din = sin->din; + int ret; + uint32_t ent, ino; + if (index < SFS_NDIRECT) { + if ((ino = din->direct[index]) != 0) { + // free the block + sfs_block_free(sfs, ino); + din->direct[index] = 0; + sin->dirty = 1; + } + return 0; + } + + index -= SFS_NDIRECT; + if (index < SFS_BLK_NENTRY) { + if ((ent = din->indirect) != 0) { + // set the entry item to 0 in the indirect block + if ((ret = sfs_bmap_free_sub_nolock(sfs, ent, index)) != 0) { + return ret; + } + } + return 0; + } + return 0; +} + +/* + * sfs_bmap_load_nolock - according to the DIR's inode and the logical index of block in inode, find the NO. of disk block. + * @sfs: sfs file system + * @sin: sfs inode in memory + * @index: the logical index of disk block in inode + * @ino_store:the NO. of disk block + */ +static int +sfs_bmap_load_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, uint32_t index, uint32_t *ino_store) { + struct sfs_disk_inode *din = sin->din; + assert(index <= din->blocks); + int ret; + uint32_t ino; + bool create = (index == din->blocks); + if ((ret = sfs_bmap_get_nolock(sfs, sin, index, create, &ino)) != 0) { + return ret; + } + assert(sfs_block_inuse(sfs, ino)); + if (create) { + din->blocks ++; + } + if (ino_store != NULL) { + *ino_store = ino; + } + return 0; +} + +/* + * sfs_bmap_truncate_nolock - free the disk block at the end of file + */ +static int +sfs_bmap_truncate_nolock(struct sfs_fs *sfs, struct sfs_inode *sin) { + struct sfs_disk_inode *din = sin->din; + assert(din->blocks != 0); + int ret; + if ((ret = sfs_bmap_free_nolock(sfs, sin, din->blocks - 1)) != 0) { + return ret; + } + din->blocks --; + sin->dirty = 1; + return 0; +} + +/* + * sfs_dirent_read_nolock - read the file entry from disk block which contains this entry + * @sfs: sfs file system + * @sin: sfs inode in memory + * @slot: the index of file entry + * @entry: file entry + */ +static int +sfs_dirent_read_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, int slot, struct sfs_disk_entry *entry) { + assert(sin->din->type == SFS_TYPE_DIR && (slot >= 0 && slot < sin->din->blocks)); + int ret; + uint32_t ino; + // according to the DIR's inode and the slot of file entry, find the index of disk block which contains this file entry + if ((ret = sfs_bmap_load_nolock(sfs, sin, slot, &ino)) != 0) { + return ret; + } + assert(sfs_block_inuse(sfs, ino)); + // read the content of file entry in the disk block + if ((ret = sfs_rbuf(sfs, entry, sizeof(struct sfs_disk_entry), ino, 0)) != 0) { + return ret; + } + entry->name[SFS_MAX_FNAME_LEN] = '\0'; + return 0; +} + +#define sfs_dirent_link_nolock_check(sfs, sin, slot, lnksin, name) \ + do { \ + int err; \ + if ((err = sfs_dirent_link_nolock(sfs, sin, slot, lnksin, name)) != 0) { \ + warn("sfs_dirent_link error: %e.\n", err); \ + } \ + } while (0) + +#define sfs_dirent_unlink_nolock_check(sfs, sin, slot, lnksin) \ + do { \ + int err; \ + if ((err = sfs_dirent_unlink_nolock(sfs, sin, slot, lnksin)) != 0) { \ + warn("sfs_dirent_unlink error: %e.\n", err); \ + } \ + } while (0) + +/* + * sfs_dirent_search_nolock - read every file entry in the DIR, compare file name with each entry->name + * If equal, then return slot and NO. of disk of this file's inode + * @sfs: sfs file system + * @sin: sfs inode in memory + * @name: the filename + * @ino_store: NO. of disk of this file (with the filename)'s inode + * @slot: logical index of file entry (NOTICE: each file entry ocupied one disk block) + * @empty_slot: the empty logical index of file entry. + */ +static int +sfs_dirent_search_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, const char *name, uint32_t *ino_store, int *slot, int *empty_slot) { + assert(strlen(name) <= SFS_MAX_FNAME_LEN); + struct sfs_disk_entry *entry; + if ((entry = kmalloc(sizeof(struct sfs_disk_entry))) == NULL) { + return -E_NO_MEM; + } + +#define set_pvalue(x, v) do { if ((x) != NULL) { *(x) = (v); } } while (0) + int ret, i, nslots = sin->din->blocks; + set_pvalue(empty_slot, nslots); + for (i = 0; i < nslots; i ++) { + if ((ret = sfs_dirent_read_nolock(sfs, sin, i, entry)) != 0) { + goto out; + } + if (entry->ino == 0) { + set_pvalue(empty_slot, i); + continue ; + } + if (strcmp(name, entry->name) == 0) { + set_pvalue(slot, i); + set_pvalue(ino_store, entry->ino); + goto out; + } + } +#undef set_pvalue + ret = -E_NOENT; +out: + kfree(entry); + return ret; +} + +/* + * sfs_dirent_findino_nolock - read all file entries in DIR's inode and find a entry->ino == ino + */ + +static int +sfs_dirent_findino_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, uint32_t ino, struct sfs_disk_entry *entry) { + int ret, i, nslots = sin->din->blocks; + for (i = 0; i < nslots; i ++) { + if ((ret = sfs_dirent_read_nolock(sfs, sin, i, entry)) != 0) { + return ret; + } + if (entry->ino == ino) { + return 0; + } + } + return -E_NOENT; +} + +/* + * sfs_lookup_once - find inode corresponding the file name in DIR's sin inode + * @sfs: sfs file system + * @sin: DIR sfs inode in memory + * @name: the file name in DIR + * @node_store: the inode corresponding the file name in DIR + * @slot: the logical index of file entry + */ +static int +sfs_lookup_once(struct sfs_fs *sfs, struct sfs_inode *sin, const char *name, struct inode **node_store, int *slot) { + int ret; + uint32_t ino; + lock_sin(sin); + { // find the NO. of disk block and logical index of file entry + ret = sfs_dirent_search_nolock(sfs, sin, name, &ino, slot, NULL); + } + unlock_sin(sin); + if (ret == 0) { + // load the content of inode with the the NO. of disk block + ret = sfs_load_inode(sfs, node_store, ino); + } + return ret; +} + +// sfs_opendir - just check the opne_flags, now support readonly +static int +sfs_opendir(struct inode *node, uint32_t open_flags) { + switch (open_flags & O_ACCMODE) { + case O_RDONLY: + break; + case O_WRONLY: + case O_RDWR: + default: + return -E_ISDIR; + } + if (open_flags & O_APPEND) { + return -E_ISDIR; + } + return 0; +} + +// sfs_openfile - open file (no use) +static int +sfs_openfile(struct inode *node, uint32_t open_flags) { + return 0; +} + +// sfs_close - close file +static int +sfs_close(struct inode *node) { + return vop_fsync(node); +} + +/* + * sfs_io_nolock - Rd/Wr a file contentfrom offset position to offset+ length disk blocks<-->buffer (in memroy) + * @sfs: sfs file system + * @sin: sfs inode in memory + * @buf: the buffer Rd/Wr + * @offset: the offset of file + * @alenp: the length need to read (is a pointer). and will RETURN the really Rd/Wr lenght + * @write: BOOL, 0 read, 1 write + */ +static int +sfs_io_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, void *buf, off_t offset, size_t *alenp, bool write) { + struct sfs_disk_inode *din = sin->din; + assert(din->type != SFS_TYPE_DIR); + off_t endpos = offset + *alenp, blkoff; + *alenp = 0; + // calculate the Rd/Wr end position + if (offset < 0 || offset >= SFS_MAX_FILE_SIZE || offset > endpos) { + return -E_INVAL; + } + if (offset == endpos) { + return 0; + } + if (endpos > SFS_MAX_FILE_SIZE) { + endpos = SFS_MAX_FILE_SIZE; + } + if (!write) { + if (offset >= din->size) { + return 0; + } + if (endpos > din->size) { + endpos = din->size; + } + } + + int (*sfs_buf_op)(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset); + int (*sfs_block_op)(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks); + if (write) { + sfs_buf_op = sfs_wbuf, sfs_block_op = sfs_wblock; + } + else { + sfs_buf_op = sfs_rbuf, sfs_block_op = sfs_rblock; + } + + int ret = 0; + size_t size, alen = 0; + uint32_t ino; + uint32_t blkno = offset / SFS_BLKSIZE; // The NO. of Rd/Wr begin block + uint32_t nblks = endpos / SFS_BLKSIZE - blkno; // The size of Rd/Wr blocks + + //LAB8:EXERCISE1 YOUR CODE HINT: call sfs_bmap_load_nolock, sfs_rbuf, sfs_rblock,etc. read different kind of blocks in file + /* + * (1) If offset isn't aligned with the first block, Rd/Wr some content from offset to the end of the first block + * NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op + * Rd/Wr size = (nblks != 0) ? (SFS_BLKSIZE - blkoff) : (endpos - offset) + * (2) Rd/Wr aligned blocks + * NOTICE: useful function: sfs_bmap_load_nolock, sfs_block_op + * (3) If end position isn't aligned with the last block, Rd/Wr some content from begin to the (endpos % SFS_BLKSIZE) of the last block + * NOTICE: useful function: sfs_bmap_load_nolock, sfs_buf_op + */ +out: + *alenp = alen; + if (offset + alen > sin->din->size) { + sin->din->size = offset + alen; + sin->dirty = 1; + } + return ret; +} + +/* + * sfs_io - Rd/Wr file. the wrapper of sfs_io_nolock + with lock protect + */ +static inline int +sfs_io(struct inode *node, struct iobuf *iob, bool write) { + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + struct sfs_inode *sin = vop_info(node, sfs_inode); + int ret; + lock_sin(sin); + { + size_t alen = iob->io_resid; + ret = sfs_io_nolock(sfs, sin, iob->io_base, iob->io_offset, &alen, write); + if (alen != 0) { + iobuf_skip(iob, alen); + } + } + unlock_sin(sin); + return ret; +} + +// sfs_read - read file +static int +sfs_read(struct inode *node, struct iobuf *iob) { + return sfs_io(node, iob, 0); +} + +// sfs_write - write file +static int +sfs_write(struct inode *node, struct iobuf *iob) { + return sfs_io(node, iob, 1); +} + +/* + * sfs_fstat - Return nlinks/block/size, etc. info about a file. The pointer is a pointer to struct stat; + */ +static int +sfs_fstat(struct inode *node, struct stat *stat) { + int ret; + memset(stat, 0, sizeof(struct stat)); + if ((ret = vop_gettype(node, &(stat->st_mode))) != 0) { + return ret; + } + struct sfs_disk_inode *din = vop_info(node, sfs_inode)->din; + stat->st_nlinks = din->nlinks; + stat->st_blocks = din->blocks; + stat->st_size = din->size; + return 0; +} + +/* + * sfs_fsync - Force any dirty inode info associated with this file to stable storage. + */ +static int +sfs_fsync(struct inode *node) { + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + struct sfs_inode *sin = vop_info(node, sfs_inode); + int ret = 0; + if (sin->dirty) { + lock_sin(sin); + { + if (sin->dirty) { + sin->dirty = 0; + if ((ret = sfs_wbuf(sfs, sin->din, sizeof(struct sfs_disk_inode), sin->ino, 0)) != 0) { + sin->dirty = 1; + } + } + } + unlock_sin(sin); + } + return ret; +} + +/* + *sfs_namefile -Compute pathname relative to filesystem root of the file and copy to the specified io buffer. + * + */ +static int +sfs_namefile(struct inode *node, struct iobuf *iob) { + struct sfs_disk_entry *entry; + if (iob->io_resid <= 2 || (entry = kmalloc(sizeof(struct sfs_disk_entry))) == NULL) { + return -E_NO_MEM; + } + + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + struct sfs_inode *sin = vop_info(node, sfs_inode); + + int ret; + char *ptr = iob->io_base + iob->io_resid; + size_t alen, resid = iob->io_resid - 2; + vop_ref_inc(node); + while (1) { + struct inode *parent; + if ((ret = sfs_lookup_once(sfs, sin, "..", &parent, NULL)) != 0) { + goto failed; + } + + uint32_t ino = sin->ino; + vop_ref_dec(node); + if (node == parent) { + vop_ref_dec(node); + break; + } + + node = parent, sin = vop_info(node, sfs_inode); + assert(ino != sin->ino && sin->din->type == SFS_TYPE_DIR); + + lock_sin(sin); + { + ret = sfs_dirent_findino_nolock(sfs, sin, ino, entry); + } + unlock_sin(sin); + + if (ret != 0) { + goto failed; + } + + if ((alen = strlen(entry->name) + 1) > resid) { + goto failed_nomem; + } + resid -= alen, ptr -= alen; + memcpy(ptr, entry->name, alen - 1); + ptr[alen - 1] = '/'; + } + alen = iob->io_resid - resid - 2; + ptr = memmove(iob->io_base + 1, ptr, alen); + ptr[-1] = '/', ptr[alen] = '\0'; + iobuf_skip(iob, alen); + kfree(entry); + return 0; + +failed_nomem: + ret = -E_NO_MEM; +failed: + vop_ref_dec(node); + kfree(entry); + return ret; +} + +/* + * sfs_getdirentry_sub_noblock - get the content of file entry in DIR + */ +static int +sfs_getdirentry_sub_nolock(struct sfs_fs *sfs, struct sfs_inode *sin, int slot, struct sfs_disk_entry *entry) { + int ret, i, nslots = sin->din->blocks; + for (i = 0; i < nslots; i ++) { + if ((ret = sfs_dirent_read_nolock(sfs, sin, i, entry)) != 0) { + return ret; + } + if (entry->ino != 0) { + if (slot == 0) { + return 0; + } + slot --; + } + } + return -E_NOENT; +} + +/* + * sfs_getdirentry - according to the iob->io_offset, calculate the dir entry's slot in disk block, + get dir entry content from the disk + */ +static int +sfs_getdirentry(struct inode *node, struct iobuf *iob) { + struct sfs_disk_entry *entry; + if ((entry = kmalloc(sizeof(struct sfs_disk_entry))) == NULL) { + return -E_NO_MEM; + } + + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + struct sfs_inode *sin = vop_info(node, sfs_inode); + + int ret, slot; + off_t offset = iob->io_offset; + if (offset < 0 || offset % sfs_dentry_size != 0) { + kfree(entry); + return -E_INVAL; + } + if ((slot = offset / sfs_dentry_size) > sin->din->blocks) { + kfree(entry); + return -E_NOENT; + } + lock_sin(sin); + if ((ret = sfs_getdirentry_sub_nolock(sfs, sin, slot, entry)) != 0) { + unlock_sin(sin); + goto out; + } + unlock_sin(sin); + ret = iobuf_move(iob, entry->name, sfs_dentry_size, 1, NULL); +out: + kfree(entry); + return ret; +} + +/* + * sfs_reclaim - Free all resources inode occupied . Called when inode is no longer in use. + */ +static int +sfs_reclaim(struct inode *node) { + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + struct sfs_inode *sin = vop_info(node, sfs_inode); + + int ret = -E_BUSY; + uint32_t ent; + lock_sfs_fs(sfs); + assert(sin->reclaim_count > 0); + if ((-- sin->reclaim_count) != 0 || inode_ref_count(node) != 0) { + goto failed_unlock; + } + if (sin->din->nlinks == 0) { + if ((ret = vop_truncate(node, 0)) != 0) { + goto failed_unlock; + } + } + if (sin->dirty) { + if ((ret = vop_fsync(node)) != 0) { + goto failed_unlock; + } + } + sfs_remove_links(sin); + unlock_sfs_fs(sfs); + + if (sin->din->nlinks == 0) { + sfs_block_free(sfs, sin->ino); + if ((ent = sin->din->indirect) != 0) { + sfs_block_free(sfs, ent); + } + } + kfree(sin->din); + vop_kill(node); + return 0; + +failed_unlock: + unlock_sfs_fs(sfs); + return ret; +} + +/* + * sfs_gettype - Return type of file. The values for file types are in sfs.h. + */ +static int +sfs_gettype(struct inode *node, uint32_t *type_store) { + struct sfs_disk_inode *din = vop_info(node, sfs_inode)->din; + switch (din->type) { + case SFS_TYPE_DIR: + *type_store = S_IFDIR; + return 0; + case SFS_TYPE_FILE: + *type_store = S_IFREG; + return 0; + case SFS_TYPE_LINK: + *type_store = S_IFLNK; + return 0; + } + panic("invalid file type %d.\n", din->type); +} + +/* + * sfs_tryseek - Check if seeking to the specified position within the file is legal. + */ +static int +sfs_tryseek(struct inode *node, off_t pos) { + if (pos < 0 || pos >= SFS_MAX_FILE_SIZE) { + return -E_INVAL; + } + struct sfs_inode *sin = vop_info(node, sfs_inode); + if (pos > sin->din->size) { + return vop_truncate(node, pos); + } + return 0; +} + +/* + * sfs_truncfile : reszie the file with new length + */ +static int +sfs_truncfile(struct inode *node, off_t len) { + if (len < 0 || len > SFS_MAX_FILE_SIZE) { + return -E_INVAL; + } + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + struct sfs_inode *sin = vop_info(node, sfs_inode); + struct sfs_disk_inode *din = sin->din; + + int ret = 0; + //new number of disk blocks of file + uint32_t nblks, tblks = ROUNDUP_DIV(len, SFS_BLKSIZE); + if (din->size == len) { + assert(tblks == din->blocks); + return 0; + } + + lock_sin(sin); + // old number of disk blocks of file + nblks = din->blocks; + if (nblks < tblks) { + // try to enlarge the file size by add new disk block at the end of file + while (nblks != tblks) { + if ((ret = sfs_bmap_load_nolock(sfs, sin, nblks, NULL)) != 0) { + goto out_unlock; + } + nblks ++; + } + } + else if (tblks < nblks) { + // try to reduce the file size + while (tblks != nblks) { + if ((ret = sfs_bmap_truncate_nolock(sfs, sin)) != 0) { + goto out_unlock; + } + nblks --; + } + } + assert(din->blocks == tblks); + din->size = len; + sin->dirty = 1; + +out_unlock: + unlock_sin(sin); + return ret; +} + +/* + * sfs_lookup - Parse path relative to the passed directory + * DIR, and hand back the inode for the file it + * refers to. + */ +static int +sfs_lookup(struct inode *node, char *path, struct inode **node_store) { + struct sfs_fs *sfs = fsop_info(vop_fs(node), sfs); + assert(*path != '\0' && *path != '/'); + vop_ref_inc(node); + struct sfs_inode *sin = vop_info(node, sfs_inode); + if (sin->din->type != SFS_TYPE_DIR) { + vop_ref_dec(node); + return -E_NOTDIR; + } + struct inode *subnode; + int ret = sfs_lookup_once(sfs, sin, path, &subnode, NULL); + + vop_ref_dec(node); + if (ret != 0) { + return ret; + } + *node_store = subnode; + return 0; +} + +// The sfs specific DIR operations correspond to the abstract operations on a inode. +static const struct inode_ops sfs_node_dirops = { + .vop_magic = VOP_MAGIC, + .vop_open = sfs_opendir, + .vop_close = sfs_close, + .vop_fstat = sfs_fstat, + .vop_fsync = sfs_fsync, + .vop_namefile = sfs_namefile, + .vop_getdirentry = sfs_getdirentry, + .vop_reclaim = sfs_reclaim, + .vop_gettype = sfs_gettype, + .vop_lookup = sfs_lookup, +}; +/// The sfs specific FILE operations correspond to the abstract operations on a inode. +static const struct inode_ops sfs_node_fileops = { + .vop_magic = VOP_MAGIC, + .vop_open = sfs_openfile, + .vop_close = sfs_close, + .vop_read = sfs_read, + .vop_write = sfs_write, + .vop_fstat = sfs_fstat, + .vop_fsync = sfs_fsync, + .vop_reclaim = sfs_reclaim, + .vop_gettype = sfs_gettype, + .vop_tryseek = sfs_tryseek, + .vop_truncate = sfs_truncfile, +}; + diff --git a/code/lab8/kern/fs/sfs/sfs_io.c b/code/lab8/kern/fs/sfs/sfs_io.c new file mode 100644 index 0000000..f3c705f --- /dev/null +++ b/code/lab8/kern/fs/sfs/sfs_io.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include + +//Basic block-level I/O routines + +/* sfs_rwblock_nolock - Basic block-level I/O routine for Rd/Wr one disk block, + * without lock protect for mutex process on Rd/Wr disk block + * @sfs: sfs_fs which will be process + * @buf: the buffer uesed for Rd/Wr + * @blkno: the NO. of disk block + * @write: BOOL: Read or Write + * @check: BOOL: if check (blono < sfs super.blocks) + */ +static int +sfs_rwblock_nolock(struct sfs_fs *sfs, void *buf, uint32_t blkno, bool write, bool check) { + assert((blkno != 0 || !check) && blkno < sfs->super.blocks); + struct iobuf __iob, *iob = iobuf_init(&__iob, buf, SFS_BLKSIZE, blkno * SFS_BLKSIZE); + return dop_io(sfs->dev, iob, write); +} + +/* sfs_rwblock - Basic block-level I/O routine for Rd/Wr N disk blocks , + * with lock protect for mutex process on Rd/Wr disk block + * @sfs: sfs_fs which will be process + * @buf: the buffer uesed for Rd/Wr + * @blkno: the NO. of disk block + * @nblks: Rd/Wr number of disk block + * @write: BOOL: Read - 0 or Write - 1 + */ +static int +sfs_rwblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks, bool write) { + int ret = 0; + lock_sfs_io(sfs); + { + while (nblks != 0) { + if ((ret = sfs_rwblock_nolock(sfs, buf, blkno, write, 1)) != 0) { + break; + } + blkno ++, nblks --; + buf += SFS_BLKSIZE; + } + } + unlock_sfs_io(sfs); + return ret; +} + +/* sfs_rblock - The Wrap of sfs_rwblock function for Rd N disk blocks , + * + * @sfs: sfs_fs which will be process + * @buf: the buffer uesed for Rd/Wr + * @blkno: the NO. of disk block + * @nblks: Rd/Wr number of disk block + */ +int +sfs_rblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks) { + return sfs_rwblock(sfs, buf, blkno, nblks, 0); +} + +/* sfs_wblock - The Wrap of sfs_rwblock function for Wr N disk blocks , + * + * @sfs: sfs_fs which will be process + * @buf: the buffer uesed for Rd/Wr + * @blkno: the NO. of disk block + * @nblks: Rd/Wr number of disk block + */ +int +sfs_wblock(struct sfs_fs *sfs, void *buf, uint32_t blkno, uint32_t nblks) { + return sfs_rwblock(sfs, buf, blkno, nblks, 1); +} + +/* sfs_rbuf - The Basic block-level I/O routine for Rd( non-block & non-aligned io) one disk block(using sfs->sfs_buffer) + * with lock protect for mutex process on Rd/Wr disk block + * @sfs: sfs_fs which will be process + * @buf: the buffer uesed for Rd + * @len: the length need to Rd + * @blkno: the NO. of disk block + * @offset: the offset in the content of disk block + */ +int +sfs_rbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset) { + assert(offset >= 0 && offset < SFS_BLKSIZE && offset + len <= SFS_BLKSIZE); + int ret; + lock_sfs_io(sfs); + { + if ((ret = sfs_rwblock_nolock(sfs, sfs->sfs_buffer, blkno, 0, 1)) == 0) { + memcpy(buf, sfs->sfs_buffer + offset, len); + } + } + unlock_sfs_io(sfs); + return ret; +} + +/* sfs_wbuf - The Basic block-level I/O routine for Wr( non-block & non-aligned io) one disk block(using sfs->sfs_buffer) + * with lock protect for mutex process on Rd/Wr disk block + * @sfs: sfs_fs which will be process + * @buf: the buffer uesed for Wr + * @len: the length need to Wr + * @blkno: the NO. of disk block + * @offset: the offset in the content of disk block + */ +int +sfs_wbuf(struct sfs_fs *sfs, void *buf, size_t len, uint32_t blkno, off_t offset) { + assert(offset >= 0 && offset < SFS_BLKSIZE && offset + len <= SFS_BLKSIZE); + int ret; + lock_sfs_io(sfs); + { + if ((ret = sfs_rwblock_nolock(sfs, sfs->sfs_buffer, blkno, 0, 1)) == 0) { + memcpy(sfs->sfs_buffer + offset, buf, len); + ret = sfs_rwblock_nolock(sfs, sfs->sfs_buffer, blkno, 1, 1); + } + } + unlock_sfs_io(sfs); + return ret; +} + +/* + * sfs_sync_super - write sfs->super (in memory) into disk (SFS_BLKN_SUPER, 1) with lock protect. + */ +int +sfs_sync_super(struct sfs_fs *sfs) { + int ret; + lock_sfs_io(sfs); + { + memset(sfs->sfs_buffer, 0, SFS_BLKSIZE); + memcpy(sfs->sfs_buffer, &(sfs->super), sizeof(sfs->super)); + ret = sfs_rwblock_nolock(sfs, sfs->sfs_buffer, SFS_BLKN_SUPER, 1, 0); + } + unlock_sfs_io(sfs); + return ret; +} + +/* + * sfs_sync_freemap - write sfs bitmap into disk (SFS_BLKN_FREEMAP, nblks) without lock protect. + */ +int +sfs_sync_freemap(struct sfs_fs *sfs) { + uint32_t nblks = sfs_freemap_blocks(&(sfs->super)); + return sfs_wblock(sfs, bitmap_getdata(sfs->freemap, NULL), SFS_BLKN_FREEMAP, nblks); +} + +/* + * sfs_clear_block - write zero info into disk (blkno, nblks) with lock protect. + * @sfs: sfs_fs which will be process + * @blkno: the NO. of disk block + * @nblks: Rd/Wr number of disk block + */ +int +sfs_clear_block(struct sfs_fs *sfs, uint32_t blkno, uint32_t nblks) { + int ret; + lock_sfs_io(sfs); + { + memset(sfs->sfs_buffer, 0, SFS_BLKSIZE); + while (nblks != 0) { + if ((ret = sfs_rwblock_nolock(sfs, sfs->sfs_buffer, blkno, 1, 1)) != 0) { + break; + } + blkno ++, nblks --; + } + } + unlock_sfs_io(sfs); + return ret; +} + diff --git a/code/lab8/kern/fs/sfs/sfs_lock.c b/code/lab8/kern/fs/sfs/sfs_lock.c new file mode 100644 index 0000000..6d83ecd --- /dev/null +++ b/code/lab8/kern/fs/sfs/sfs_lock.c @@ -0,0 +1,44 @@ +#include +#include +#include + + +/* + * lock_sfs_fs - lock the process of SFS Filesystem Rd/Wr Disk Block + * + * called by: sfs_load_inode, sfs_sync, sfs_reclaim + */ +void +lock_sfs_fs(struct sfs_fs *sfs) { + down(&(sfs->fs_sem)); +} + +/* + * lock_sfs_io - lock the process of SFS File Rd/Wr Disk Block + * + * called by: sfs_rwblock, sfs_clear_block, sfs_sync_super + */ +void +lock_sfs_io(struct sfs_fs *sfs) { + down(&(sfs->io_sem)); +} + +/* + * unlock_sfs_fs - unlock the process of SFS Filesystem Rd/Wr Disk Block + * + * called by: sfs_load_inode, sfs_sync, sfs_reclaim + */ +void +unlock_sfs_fs(struct sfs_fs *sfs) { + up(&(sfs->fs_sem)); +} + +/* + * unlock_sfs_io - unlock the process of sfs Rd/Wr Disk Block + * + * called by: sfs_rwblock sfs_clear_block sfs_sync_super + */ +void +unlock_sfs_io(struct sfs_fs *sfs) { + up(&(sfs->io_sem)); +} diff --git a/code/lab8/kern/fs/swap/swapfs.c b/code/lab8/kern/fs/swap/swapfs.c new file mode 100644 index 0000000..d9f6090 --- /dev/null +++ b/code/lab8/kern/fs/swap/swapfs.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include +#include +#include + +void +swapfs_init(void) { + static_assert((PGSIZE % SECTSIZE) == 0); + if (!ide_device_valid(SWAP_DEV_NO)) { + panic("swap fs isn't available.\n"); + } + max_swap_offset = ide_device_size(SWAP_DEV_NO) / (PGSIZE / SECTSIZE); +} + +int +swapfs_read(swap_entry_t entry, struct Page *page) { + return ide_read_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + +int +swapfs_write(swap_entry_t entry, struct Page *page) { + return ide_write_secs(SWAP_DEV_NO, swap_offset(entry) * PAGE_NSECT, page2kva(page), PAGE_NSECT); +} + diff --git a/code/lab8/kern/fs/swap/swapfs.h b/code/lab8/kern/fs/swap/swapfs.h new file mode 100644 index 0000000..904fb2e --- /dev/null +++ b/code/lab8/kern/fs/swap/swapfs.h @@ -0,0 +1,12 @@ +#ifndef __KERN_FS_SWAP_SWAPFS_H__ +#define __KERN_FS_SWAP_SWAPFS_H__ + +#include +#include + +void swapfs_init(void); +int swapfs_read(swap_entry_t entry, struct Page *page); +int swapfs_write(swap_entry_t entry, struct Page *page); + +#endif /* !__KERN_FS_SWAP_SWAPFS_H__ */ + diff --git a/code/lab8/kern/fs/sysfile.c b/code/lab8/kern/fs/sysfile.c new file mode 100644 index 0000000..0dd8e58 --- /dev/null +++ b/code/lab8/kern/fs/sysfile.c @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IOBUF_SIZE 4096 + +/* copy_path - copy path name */ +static int +copy_path(char **to, const char *from) { + struct mm_struct *mm = current->mm; + char *buffer; + if ((buffer = kmalloc(FS_MAX_FPATH_LEN + 1)) == NULL) { + return -E_NO_MEM; + } + lock_mm(mm); + if (!copy_string(mm, buffer, from, FS_MAX_FPATH_LEN + 1)) { + unlock_mm(mm); + goto failed_cleanup; + } + unlock_mm(mm); + *to = buffer; + return 0; + +failed_cleanup: + kfree(buffer); + return -E_INVAL; +} + +/* sysfile_open - open file */ +int +sysfile_open(const char *__path, uint32_t open_flags) { + int ret; + char *path; + if ((ret = copy_path(&path, __path)) != 0) { + return ret; + } + ret = file_open(path, open_flags); + kfree(path); + return ret; +} + +/* sysfile_close - close file */ +int +sysfile_close(int fd) { + return file_close(fd); +} + +/* sysfile_read - read file */ +int +sysfile_read(int fd, void *base, size_t len) { + struct mm_struct *mm = current->mm; + if (len == 0) { + return 0; + } + if (!file_testfd(fd, 1, 0)) { + return -E_INVAL; + } + void *buffer; + if ((buffer = kmalloc(IOBUF_SIZE)) == NULL) { + return -E_NO_MEM; + } + + int ret = 0; + size_t copied = 0, alen; + while (len != 0) { + if ((alen = IOBUF_SIZE) > len) { + alen = len; + } + ret = file_read(fd, buffer, alen, &alen); + if (alen != 0) { + lock_mm(mm); + { + if (copy_to_user(mm, base, buffer, alen)) { + assert(len >= alen); + base += alen, len -= alen, copied += alen; + } + else if (ret == 0) { + ret = -E_INVAL; + } + } + unlock_mm(mm); + } + if (ret != 0 || alen == 0) { + goto out; + } + } + +out: + kfree(buffer); + if (copied != 0) { + return copied; + } + return ret; +} + +/* sysfile_write - write file */ +int +sysfile_write(int fd, void *base, size_t len) { + struct mm_struct *mm = current->mm; + if (len == 0) { + return 0; + } + if (!file_testfd(fd, 0, 1)) { + return -E_INVAL; + } + void *buffer; + if ((buffer = kmalloc(IOBUF_SIZE)) == NULL) { + return -E_NO_MEM; + } + + int ret = 0; + size_t copied = 0, alen; + while (len != 0) { + if ((alen = IOBUF_SIZE) > len) { + alen = len; + } + lock_mm(mm); + { + if (!copy_from_user(mm, buffer, base, alen, 0)) { + ret = -E_INVAL; + } + } + unlock_mm(mm); + if (ret == 0) { + ret = file_write(fd, buffer, alen, &alen); + if (alen != 0) { + assert(len >= alen); + base += alen, len -= alen, copied += alen; + } + } + if (ret != 0 || alen == 0) { + goto out; + } + } + +out: + kfree(buffer); + if (copied != 0) { + return copied; + } + return ret; +} + +/* sysfile_seek - seek file */ +int +sysfile_seek(int fd, off_t pos, int whence) { + return file_seek(fd, pos, whence); +} + +/* sysfile_fstat - stat file */ +int +sysfile_fstat(int fd, struct stat *__stat) { + struct mm_struct *mm = current->mm; + int ret; + struct stat __local_stat, *stat = &__local_stat; + if ((ret = file_fstat(fd, stat)) != 0) { + return ret; + } + + lock_mm(mm); + { + if (!copy_to_user(mm, __stat, stat, sizeof(struct stat))) { + ret = -E_INVAL; + } + } + unlock_mm(mm); + return ret; +} + +/* sysfile_fsync - sync file */ +int +sysfile_fsync(int fd) { + return file_fsync(fd); +} + +/* sysfile_chdir - change dir */ +int +sysfile_chdir(const char *__path) { + int ret; + char *path; + if ((ret = copy_path(&path, __path)) != 0) { + return ret; + } + ret = vfs_chdir(path); + kfree(path); + return ret; +} + +/* sysfile_link - link file */ +int +sysfile_link(const char *__path1, const char *__path2) { + int ret; + char *old_path, *new_path; + if ((ret = copy_path(&old_path, __path1)) != 0) { + return ret; + } + if ((ret = copy_path(&new_path, __path2)) != 0) { + kfree(old_path); + return ret; + } + ret = vfs_link(old_path, new_path); + kfree(old_path), kfree(new_path); + return ret; +} + +/* sysfile_rename - rename file */ +int +sysfile_rename(const char *__path1, const char *__path2) { + int ret; + char *old_path, *new_path; + if ((ret = copy_path(&old_path, __path1)) != 0) { + return ret; + } + if ((ret = copy_path(&new_path, __path2)) != 0) { + kfree(old_path); + return ret; + } + ret = vfs_rename(old_path, new_path); + kfree(old_path), kfree(new_path); + return ret; +} + +/* sysfile_unlink - unlink file */ +int +sysfile_unlink(const char *__path) { + int ret; + char *path; + if ((ret = copy_path(&path, __path)) != 0) { + return ret; + } + ret = vfs_unlink(path); + kfree(path); + return ret; +} + +/* sysfile_get cwd - get current working directory */ +int +sysfile_getcwd(char *buf, size_t len) { + struct mm_struct *mm = current->mm; + if (len == 0) { + return -E_INVAL; + } + + int ret = -E_INVAL; + lock_mm(mm); + { + if (user_mem_check(mm, (uintptr_t)buf, len, 1)) { + struct iobuf __iob, *iob = iobuf_init(&__iob, buf, len, 0); + ret = vfs_getcwd(iob); + } + } + unlock_mm(mm); + return ret; +} + +/* sysfile_getdirentry - get the file entry in DIR */ +int +sysfile_getdirentry(int fd, struct dirent *__direntp) { + struct mm_struct *mm = current->mm; + struct dirent *direntp; + if ((direntp = kmalloc(sizeof(struct dirent))) == NULL) { + return -E_NO_MEM; + } + + int ret = 0; + lock_mm(mm); + { + if (!copy_from_user(mm, &(direntp->offset), &(__direntp->offset), sizeof(direntp->offset), 1)) { + ret = -E_INVAL; + } + } + unlock_mm(mm); + + if (ret != 0 || (ret = file_getdirentry(fd, direntp)) != 0) { + goto out; + } + + lock_mm(mm); + { + if (!copy_to_user(mm, __direntp, direntp, sizeof(struct dirent))) { + ret = -E_INVAL; + } + } + unlock_mm(mm); + +out: + kfree(direntp); + return ret; +} + +/* sysfile_dup - duplicate fd1 to fd2 */ +int +sysfile_dup(int fd1, int fd2) { + return file_dup(fd1, fd2); +} + +int +sysfile_pipe(int *fd_store) { + return -E_UNIMP; +} + +int +sysfile_mkfifo(const char *__name, uint32_t open_flags) { + return -E_UNIMP; +} + diff --git a/code/lab8/kern/fs/sysfile.h b/code/lab8/kern/fs/sysfile.h new file mode 100644 index 0000000..bc060e9 --- /dev/null +++ b/code/lab8/kern/fs/sysfile.h @@ -0,0 +1,28 @@ +#ifndef __KERN_FS_SYSFILE_H__ +#define __KERN_FS_SYSFILE_H__ + +#include + +struct stat; +struct dirent; + +int sysfile_open(const char *path, uint32_t open_flags); // Open or create a file. FLAGS/MODE per the syscall. +int sysfile_close(int fd); // Close a vnode opened +int sysfile_read(int fd, void *base, size_t len); // Read file +int sysfile_write(int fd, void *base, size_t len); // Write file +int sysfile_seek(int fd, off_t pos, int whence); // Seek file +int sysfile_fstat(int fd, struct stat *stat); // Stat file +int sysfile_fsync(int fd); // Sync file +int sysfile_chdir(const char *path); // change DIR +int sysfile_mkdir(const char *path); // create DIR +int sysfile_link(const char *path1, const char *path2); // set a path1's link as path2 +int sysfile_rename(const char *path1, const char *path2); // rename file +int sysfile_unlink(const char *path); // unlink a path +int sysfile_getcwd(char *buf, size_t len); // get current working directory +int sysfile_getdirentry(int fd, struct dirent *direntp); // get the file entry in DIR +int sysfile_dup(int fd1, int fd2); // duplicate file +int sysfile_pipe(int *fd_store); // build PIPE +int sysfile_mkfifo(const char *name, uint32_t open_flags); // build named PIPE + +#endif /* !__KERN_FS_SYSFILE_H__ */ + diff --git a/code/lab8/kern/fs/vfs/inode.c b/code/lab8/kern/fs/vfs/inode.c new file mode 100644 index 0000000..277b0f0 --- /dev/null +++ b/code/lab8/kern/fs/vfs/inode.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * __alloc_inode - alloc a inode structure and initialize in_type + * */ +struct inode * +__alloc_inode(int type) { + struct inode *node; + if ((node = kmalloc(sizeof(struct inode))) != NULL) { + node->in_type = type; + } + return node; +} + +/* * + * inode_init - initialize a inode structure + * invoked by vop_init + * */ +void +inode_init(struct inode *node, const struct inode_ops *ops, struct fs *fs) { + atomic_set(&(node->ref_count), 0); + atomic_set(&(node->open_count), 0); + node->in_ops = ops, node->in_fs = fs; + vop_ref_inc(node); +} + +/* * + * inode_kill - kill a inode structure + * invoked by vop_kill + * */ +void +inode_kill(struct inode *node) { + assert(inode_ref_count(node) == 0); + assert(inode_open_count(node) == 0); + kfree(node); +} + +/* * + * inode_ref_inc - increment ref_count + * invoked by vop_ref_inc + * */ +int +inode_ref_inc(struct inode *node) { + return atomic_add_return(&(node->ref_count), 1); +} + +/* * + * inode_ref_dec - decrement ref_count + * invoked by vop_ref_dec + * calls vop_reclaim if the ref_count hits zero + * */ +int +inode_ref_dec(struct inode *node) { + assert(inode_ref_count(node) > 0); + int ref_count, ret; + if ((ref_count = atomic_sub_return(&(node->ref_count), 1)) == 0) { + if ((ret = vop_reclaim(node)) != 0 && ret != -E_BUSY) { + cprintf("vfs: warning: vop_reclaim: %e.\n", ret); + } + } + return ref_count; +} + +/* * + * inode_open_inc - increment the open_count + * invoked by vop_open_inc + * */ +int +inode_open_inc(struct inode *node) { + return atomic_add_return(&(node->open_count), 1); +} + +/* * + * inode_open_dec - decrement the open_count + * invoked by vop_open_dec + * calls vop_close if the open_count hits zero + * */ +int +inode_open_dec(struct inode *node) { + assert(inode_open_count(node) > 0); + int open_count, ret; + if ((open_count = atomic_sub_return(&(node->open_count), 1)) == 0) { + if ((ret = vop_close(node)) != 0) { + cprintf("vfs: warning: vop_close: %e.\n", ret); + } + } + return open_count; +} + +/* * + * inode_check - check the various things being valid + * called before all vop_* calls + * */ +void +inode_check(struct inode *node, const char *opstr) { + assert(node != NULL && node->in_ops != NULL); + assert(node->in_ops->vop_magic == VOP_MAGIC); + int ref_count = inode_ref_count(node), open_count = inode_open_count(node); + assert(ref_count >= open_count && open_count >= 0); + assert(ref_count < MAX_INODE_COUNT && open_count < MAX_INODE_COUNT); +} + diff --git a/code/lab8/kern/fs/vfs/inode.h b/code/lab8/kern/fs/vfs/inode.h new file mode 100644 index 0000000..325981a --- /dev/null +++ b/code/lab8/kern/fs/vfs/inode.h @@ -0,0 +1,248 @@ +#ifndef __KERN_FS_VFS_INODE_H__ +#define __KERN_FS_VFS_INODE_H__ + +#include +#include +#include +#include +#include + +struct stat; +struct iobuf; + +/* + * A struct inode is an abstract representation of a file. + * + * It is an interface that allows the kernel's filesystem-independent + * code to interact usefully with multiple sets of filesystem code. + */ + +/* + * Abstract low-level file. + * + * Note: in_info is Filesystem-specific data, in_type is the inode type + * + * open_count is managed using VOP_INCOPEN and VOP_DECOPEN by + * vfs_open() and vfs_close(). Code above the VFS layer should not + * need to worry about it. + */ +struct inode { + union { + struct device __device_info; + struct sfs_inode __sfs_inode_info; + } in_info; + enum { + inode_type_device_info = 0x1234, + inode_type_sfs_inode_info, + } in_type; + atomic_t ref_count; + atomic_t open_count; + struct fs *in_fs; + const struct inode_ops *in_ops; +}; + +#define __in_type(type) inode_type_##type##_info + +#define check_inode_type(node, type) ((node)->in_type == __in_type(type)) + +#define __vop_info(node, type) \ + ({ \ + struct inode *__node = (node); \ + assert(__node != NULL && check_inode_type(__node, type)); \ + &(__node->in_info.__##type##_info); \ + }) + +#define vop_info(node, type) __vop_info(node, type) + +#define info2node(info, type) \ + to_struct((info), struct inode, in_info.__##type##_info) + +struct inode *__alloc_inode(int type); + +#define alloc_inode(type) __alloc_inode(__in_type(type)) + +#define MAX_INODE_COUNT 0x10000 + +int inode_ref_inc(struct inode *node); +int inode_ref_dec(struct inode *node); +int inode_open_inc(struct inode *node); +int inode_open_dec(struct inode *node); + +void inode_init(struct inode *node, const struct inode_ops *ops, struct fs *fs); +void inode_kill(struct inode *node); + +#define VOP_MAGIC 0x8c4ba476 + +/* + * Abstract operations on a inode. + * + * These are used in the form VOP_FOO(inode, args), which are macros + * that expands to inode->inode_ops->vop_foo(inode, args). The operations + * "foo" are: + * + * vop_open - Called on open() of a file. Can be used to + * reject illegal or undesired open modes. Note that + * various operations can be performed without the + * file actually being opened. + * The inode need not look at O_CREAT, O_EXCL, or + * O_TRUNC, as these are handled in the VFS layer. + * + * VOP_EACHOPEN should not be called directly from + * above the VFS layer - use vfs_open() to open inodes. + * This maintains the open count so VOP_LASTCLOSE can + * be called at the right time. + * + * vop_close - To be called on *last* close() of a file. + * + * VOP_LASTCLOSE should not be called directly from + * above the VFS layer - use vfs_close() to close + * inodes opened with vfs_open(). + * + * vop_reclaim - Called when inode is no longer in use. Note that + * this may be substantially after vop_lastclose is + * called. + * + ***************************************** + * + * vop_read - Read data from file to uio, at offset specified + * in the uio, updating uio_resid to reflect the + * amount read, and updating uio_offset to match. + * Not allowed on directories or symlinks. + * + * vop_getdirentry - Read a single filename from a directory into a + * uio, choosing what name based on the offset + * field in the uio, and updating that field. + * Unlike with I/O on regular files, the value of + * the offset field is not interpreted outside + * the filesystem and thus need not be a byte + * count. However, the uio_resid field should be + * handled in the normal fashion. + * On non-directory objects, return ENOTDIR. + * + * vop_write - Write data from uio to file at offset specified + * in the uio, updating uio_resid to reflect the + * amount written, and updating uio_offset to match. + * Not allowed on directories or symlinks. + * + * vop_ioctl - Perform ioctl operation OP on file using data + * DATA. The interpretation of the data is specific + * to each ioctl. + * + * vop_fstat -Return info about a file. The pointer is a + * pointer to struct stat; see stat.h. + * + * vop_gettype - Return type of file. The values for file types + * are in sfs.h. + * + * vop_tryseek - Check if seeking to the specified position within + * the file is legal. (For instance, all seeks + * are illegal on serial port devices, and seeks + * past EOF on files whose sizes are fixed may be + * as well.) + * + * vop_fsync - Force any dirty buffers associated with this file + * to stable storage. + * + * vop_truncate - Forcibly set size of file to the length passed + * in, discarding any excess blocks. + * + * vop_namefile - Compute pathname relative to filesystem root + * of the file and copy to the specified io buffer. + * Need not work on objects that are not + * directories. + * + ***************************************** + * + * vop_creat - Create a regular file named NAME in the passed + * directory DIR. If boolean EXCL is true, fail if + * the file already exists; otherwise, use the + * existing file if there is one. Hand back the + * inode for the file as per vop_lookup. + * + ***************************************** + * + * vop_lookup - Parse PATHNAME relative to the passed directory + * DIR, and hand back the inode for the file it + * refers to. May destroy PATHNAME. Should increment + * refcount on inode handed back. + */ +struct inode_ops { + unsigned long vop_magic; + int (*vop_open)(struct inode *node, uint32_t open_flags); + int (*vop_close)(struct inode *node); + int (*vop_read)(struct inode *node, struct iobuf *iob); + int (*vop_write)(struct inode *node, struct iobuf *iob); + int (*vop_fstat)(struct inode *node, struct stat *stat); + int (*vop_fsync)(struct inode *node); + int (*vop_namefile)(struct inode *node, struct iobuf *iob); + int (*vop_getdirentry)(struct inode *node, struct iobuf *iob); + int (*vop_reclaim)(struct inode *node); + int (*vop_gettype)(struct inode *node, uint32_t *type_store); + int (*vop_tryseek)(struct inode *node, off_t pos); + int (*vop_truncate)(struct inode *node, off_t len); + int (*vop_create)(struct inode *node, const char *name, bool excl, struct inode **node_store); + int (*vop_lookup)(struct inode *node, char *path, struct inode **node_store); + int (*vop_ioctl)(struct inode *node, int op, void *data); +}; + +/* + * Consistency check + */ +void inode_check(struct inode *node, const char *opstr); + +#define __vop_op(node, sym) \ + ({ \ + struct inode *__node = (node); \ + assert(__node != NULL && __node->in_ops != NULL && __node->in_ops->vop_##sym != NULL); \ + inode_check(__node, #sym); \ + __node->in_ops->vop_##sym; \ + }) + +#define vop_open(node, open_flags) (__vop_op(node, open)(node, open_flags)) +#define vop_close(node) (__vop_op(node, close)(node)) +#define vop_read(node, iob) (__vop_op(node, read)(node, iob)) +#define vop_write(node, iob) (__vop_op(node, write)(node, iob)) +#define vop_fstat(node, stat) (__vop_op(node, fstat)(node, stat)) +#define vop_fsync(node) (__vop_op(node, fsync)(node)) +#define vop_namefile(node, iob) (__vop_op(node, namefile)(node, iob)) +#define vop_getdirentry(node, iob) (__vop_op(node, getdirentry)(node, iob)) +#define vop_reclaim(node) (__vop_op(node, reclaim)(node)) +#define vop_ioctl(node, op, data) (__vop_op(node, ioctl)(node, op, data)) +#define vop_gettype(node, type_store) (__vop_op(node, gettype)(node, type_store)) +#define vop_tryseek(node, pos) (__vop_op(node, tryseek)(node, pos)) +#define vop_truncate(node, len) (__vop_op(node, truncate)(node, len)) +#define vop_create(node, name, excl, node_store) (__vop_op(node, create)(node, name, excl, node_store)) +#define vop_lookup(node, path, node_store) (__vop_op(node, lookup)(node, path, node_store)) + + +#define vop_fs(node) ((node)->in_fs) +#define vop_init(node, ops, fs) inode_init(node, ops, fs) +#define vop_kill(node) inode_kill(node) + +/* + * Reference count manipulation (handled above filesystem level) + */ +#define vop_ref_inc(node) inode_ref_inc(node) +#define vop_ref_dec(node) inode_ref_dec(node) +/* + * Open count manipulation (handled above filesystem level) + * + * VOP_INCOPEN is called by vfs_open. VOP_DECOPEN is called by vfs_close. + * Neither of these should need to be called from above the vfs layer. + */ +#define vop_open_inc(node) inode_open_inc(node) +#define vop_open_dec(node) inode_open_dec(node) + + +static inline int +inode_ref_count(struct inode *node) { + return atomic_read(&(node->ref_count)); +} + +static inline int +inode_open_count(struct inode *node) { + return atomic_read(&(node->open_count)); +} + +#endif /* !__KERN_FS_VFS_INODE_H__ */ + diff --git a/code/lab8/kern/fs/vfs/vfs.c b/code/lab8/kern/fs/vfs/vfs.c new file mode 100644 index 0000000..29e8707 --- /dev/null +++ b/code/lab8/kern/fs/vfs/vfs.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static semaphore_t bootfs_sem; +static struct inode *bootfs_node = NULL; + +extern void vfs_devlist_init(void); + +// __alloc_fs - allocate memory for fs, and set fs type +struct fs * +__alloc_fs(int type) { + struct fs *fs; + if ((fs = kmalloc(sizeof(struct fs))) != NULL) { + fs->fs_type = type; + } + return fs; +} + +// vfs_init - vfs initialize +void +vfs_init(void) { + sem_init(&bootfs_sem, 1); + vfs_devlist_init(); +} + +// lock_bootfs - lock for bootfs +static void +lock_bootfs(void) { + down(&bootfs_sem); +} +// ulock_bootfs - ulock for bootfs +static void +unlock_bootfs(void) { + up(&bootfs_sem); +} + +// change_bootfs - set the new fs inode +static void +change_bootfs(struct inode *node) { + struct inode *old; + lock_bootfs(); + { + old = bootfs_node, bootfs_node = node; + } + unlock_bootfs(); + if (old != NULL) { + vop_ref_dec(old); + } +} + +// vfs_set_bootfs - change the dir of file system +int +vfs_set_bootfs(char *fsname) { + struct inode *node = NULL; + if (fsname != NULL) { + char *s; + if ((s = strchr(fsname, ':')) == NULL || s[1] != '\0') { + return -E_INVAL; + } + int ret; + if ((ret = vfs_chdir(fsname)) != 0) { + return ret; + } + if ((ret = vfs_get_curdir(&node)) != 0) { + return ret; + } + } + change_bootfs(node); + return 0; +} + +// vfs_get_bootfs - get the inode of bootfs +int +vfs_get_bootfs(struct inode **node_store) { + struct inode *node = NULL; + if (bootfs_node != NULL) { + lock_bootfs(); + { + if ((node = bootfs_node) != NULL) { + vop_ref_inc(bootfs_node); + } + } + unlock_bootfs(); + } + if (node == NULL) { + return -E_NOENT; + } + *node_store = node; + return 0; +} + diff --git a/code/lab8/kern/fs/vfs/vfs.h b/code/lab8/kern/fs/vfs/vfs.h new file mode 100644 index 0000000..7ff8a10 --- /dev/null +++ b/code/lab8/kern/fs/vfs/vfs.h @@ -0,0 +1,191 @@ +#ifndef __KERN_FS_VFS_VFS_H__ +#define __KERN_FS_VFS_VFS_H__ + +#include +#include +#include + +struct inode; // abstract structure for an on-disk file (inode.h) +struct device; // abstract structure for a device (dev.h) +struct iobuf; // kernel or userspace I/O buffer (iobuf.h) + +/* + * Abstract filesystem. (Or device accessible as a file.) + * + * Information: + * fs_info : filesystem-specific data (sfs_fs) + * fs_type : filesystem type + * Operations: + * + * fs_sync - Flush all dirty buffers to disk. + * fs_get_root - Return root inode of filesystem. + * fs_unmount - Attempt unmount of filesystem. + * fs_cleanup - Cleanup of filesystem.??? + * + * + * fs_get_root should increment the refcount of the inode returned. + * It should not ever return NULL. + * + * If fs_unmount returns an error, the filesystem stays mounted, and + * consequently the struct fs instance should remain valid. On success, + * however, the filesystem object and all storage associated with the + * filesystem should have been discarded/released. + * + */ +struct fs { + union { + struct sfs_fs __sfs_info; + } fs_info; // filesystem-specific data + enum { + fs_type_sfs_info, + } fs_type; // filesystem type + int (*fs_sync)(struct fs *fs); // Flush all dirty buffers to disk + struct inode *(*fs_get_root)(struct fs *fs); // Return root inode of filesystem. + int (*fs_unmount)(struct fs *fs); // Attempt unmount of filesystem. + void (*fs_cleanup)(struct fs *fs); // Cleanup of filesystem.??? +}; + +#define __fs_type(type) fs_type_##type##_info + +#define check_fs_type(fs, type) ((fs)->fs_type == __fs_type(type)) + +#define __fsop_info(_fs, type) ({ \ + struct fs *__fs = (_fs); \ + assert(__fs != NULL && check_fs_type(__fs, type)); \ + &(__fs->fs_info.__##type##_info); \ + }) + +#define fsop_info(fs, type) __fsop_info(fs, type) + +#define info2fs(info, type) \ + to_struct((info), struct fs, fs_info.__##type##_info) + +struct fs *__alloc_fs(int type); + +#define alloc_fs(type) __alloc_fs(__fs_type(type)) + +// Macros to shorten the calling sequences. +#define fsop_sync(fs) ((fs)->fs_sync(fs)) +#define fsop_get_root(fs) ((fs)->fs_get_root(fs)) +#define fsop_unmount(fs) ((fs)->fs_unmount(fs)) +#define fsop_cleanup(fs) ((fs)->fs_cleanup(fs)) + +/* + * Virtual File System layer functions. + * + * The VFS layer translates operations on abstract on-disk files or + * pathnames to operations on specific files on specific filesystems. + */ +void vfs_init(void); +void vfs_cleanup(void); +void vfs_devlist_init(void); + +/* + * VFS layer low-level operations. + * See inode.h for direct operations on inodes. + * See fs.h for direct operations on filesystems/devices. + * + * vfs_set_curdir - change current directory of current thread by inode + * vfs_get_curdir - retrieve inode of current directory of current thread + * vfs_get_root - get root inode for the filesystem named DEVNAME + * vfs_get_devname - get mounted device name for the filesystem passed in + */ +int vfs_set_curdir(struct inode *dir); +int vfs_get_curdir(struct inode **dir_store); +int vfs_get_root(const char *devname, struct inode **root_store); +const char *vfs_get_devname(struct fs *fs); + + +/* + * VFS layer high-level operations on pathnames + * Because namei may destroy pathnames, these all may too. + * + * vfs_open - Open or create a file. FLAGS/MODE per the syscall. + * vfs_close - Close a inode opened with vfs_open. Does not fail. + * (See vfspath.c for a discussion of why.) + * vfs_link - Create a hard link to a file. + * vfs_symlink - Create a symlink PATH containing contents CONTENTS. + * vfs_readlink - Read contents of a symlink into a uio. + * vfs_mkdir - Create a directory. MODE per the syscall. + * vfs_unlink - Delete a file/directory. + * vfs_rename - rename a file. + * vfs_chdir - Change current directory of current thread by name. + * vfs_getcwd - Retrieve name of current directory of current thread. + * + */ +int vfs_open(char *path, uint32_t open_flags, struct inode **inode_store); +int vfs_close(struct inode *node); +int vfs_link(char *old_path, char *new_path); +int vfs_symlink(char *old_path, char *new_path); +int vfs_readlink(char *path, struct iobuf *iob); +int vfs_mkdir(char *path); +int vfs_unlink(char *path); +int vfs_rename(char *old_path, char *new_path); +int vfs_chdir(char *path); +int vfs_getcwd(struct iobuf *iob); + + +/* + * VFS layer mid-level operations. + * + * vfs_lookup - Like VOP_LOOKUP, but takes a full device:path name, + * or a name relative to the current directory, and + * goes to the correct filesystem. + * vfs_lookparent - Likewise, for VOP_LOOKPARENT. + * + * Both of these may destroy the path passed in. + */ +int vfs_lookup(char *path, struct inode **node_store); +int vfs_lookup_parent(char *path, struct inode **node_store, char **endp); + +/* + * Misc + * + * vfs_set_bootfs - Set the filesystem that paths beginning with a + * slash are sent to. If not set, these paths fail + * with ENOENT. The argument should be the device + * name or volume name for the filesystem (such as + * "lhd0:") but need not have the trailing colon. + * + * vfs_get_bootfs - return the inode of the bootfs filesystem. + * + * vfs_add_fs - Add a hardwired filesystem to the VFS named device + * list. It will be accessible as "devname:". This is + * intended for filesystem-devices like emufs, and + * gizmos like Linux procfs or BSD kernfs, not for + * mounting filesystems on disk devices. + * + * vfs_add_dev - Add a device to the VFS named device list. If + * MOUNTABLE is zero, the device will be accessible + * as "DEVNAME:". If the mountable flag is set, the + * device will be accessible as "DEVNAMEraw:" and + * mountable under the name "DEVNAME". Thus, the + * console, added with MOUNTABLE not set, would be + * accessed by pathname as "con:", and lhd0, added + * with mountable set, would be accessed by + * pathname as "lhd0raw:" and mounted by passing + * "lhd0" to vfs_mount. + * + * vfs_mount - Attempt to mount a filesystem on a device. The + * device named by DEVNAME will be looked up and + * passed, along with DATA, to the supplied function + * MOUNTFUNC, which should create a struct fs and + * return it in RESULT. + * + * vfs_unmount - Unmount the filesystem presently mounted on the + * specified device. + * + * vfs_unmountall - Unmount all mounted filesystems. + */ +int vfs_set_bootfs(char *fsname); +int vfs_get_bootfs(struct inode **node_store); + +int vfs_add_fs(const char *devname, struct fs *fs); +int vfs_add_dev(const char *devname, struct inode *devnode, bool mountable); + +int vfs_mount(const char *devname, int (*mountfunc)(struct device *dev, struct fs **fs_store)); +int vfs_unmount(const char *devname); +int vfs_unmount_all(void); + +#endif /* !__KERN_FS_VFS_VFS_H__ */ + diff --git a/code/lab8/kern/fs/vfs/vfsdev.c b/code/lab8/kern/fs/vfs/vfsdev.c new file mode 100644 index 0000000..f652618 --- /dev/null +++ b/code/lab8/kern/fs/vfs/vfsdev.c @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// device info entry in vdev_list +typedef struct { + const char *devname; + struct inode *devnode; + struct fs *fs; + bool mountable; + list_entry_t vdev_link; +} vfs_dev_t; + +#define le2vdev(le, member) \ + to_struct((le), vfs_dev_t, member) + +static list_entry_t vdev_list; // device info list in vfs layer +static semaphore_t vdev_list_sem; + +static void +lock_vdev_list(void) { + down(&vdev_list_sem); +} + +static void +unlock_vdev_list(void) { + up(&vdev_list_sem); +} + +void +vfs_devlist_init(void) { + list_init(&vdev_list); + sem_init(&vdev_list_sem, 1); +} + +// vfs_cleanup - finally clean (or sync) fs +void +vfs_cleanup(void) { + if (!list_empty(&vdev_list)) { + lock_vdev_list(); + { + list_entry_t *list = &vdev_list, *le = list; + while ((le = list_next(le)) != list) { + vfs_dev_t *vdev = le2vdev(le, vdev_link); + if (vdev->fs != NULL) { + fsop_cleanup(vdev->fs); + } + } + } + unlock_vdev_list(); + } +} + +/* + * vfs_get_root - Given a device name (stdin, stdout, etc.), hand + * back an appropriate inode. + */ +int +vfs_get_root(const char *devname, struct inode **node_store) { + assert(devname != NULL); + int ret = -E_NO_DEV; + if (!list_empty(&vdev_list)) { + lock_vdev_list(); + { + list_entry_t *list = &vdev_list, *le = list; + while ((le = list_next(le)) != list) { + vfs_dev_t *vdev = le2vdev(le, vdev_link); + if (strcmp(devname, vdev->devname) == 0) { + struct inode *found = NULL; + if (vdev->fs != NULL) { + found = fsop_get_root(vdev->fs); + } + else if (!vdev->mountable) { + vop_ref_inc(vdev->devnode); + found = vdev->devnode; + } + if (found != NULL) { + ret = 0, *node_store = found; + } + else { + ret = -E_NA_DEV; + } + break; + } + } + } + unlock_vdev_list(); + } + return ret; +} + +/* + * vfs_get_devname - Given a filesystem, hand back the name of the device it's mounted on. + */ +const char * +vfs_get_devname(struct fs *fs) { + assert(fs != NULL); + list_entry_t *list = &vdev_list, *le = list; + while ((le = list_next(le)) != list) { + vfs_dev_t *vdev = le2vdev(le, vdev_link); + if (vdev->fs == fs) { + return vdev->devname; + } + } + return NULL; +} + +/* + * check_devname_confilct - Is there alreadily device which has the same name? + */ +static bool +check_devname_conflict(const char *devname) { + list_entry_t *list = &vdev_list, *le = list; + while ((le = list_next(le)) != list) { + vfs_dev_t *vdev = le2vdev(le, vdev_link); + if (strcmp(vdev->devname, devname) == 0) { + return 0; + } + } + return 1; +} + + +/* +* vfs_do_add - Add a new device to the VFS layer's device table. +* +* If "mountable" is set, the device will be treated as one that expects +* to have a filesystem mounted on it, and a raw device will be created +* for direct access. +*/ +static int +vfs_do_add(const char *devname, struct inode *devnode, struct fs *fs, bool mountable) { + assert(devname != NULL); + assert((devnode == NULL && !mountable) || (devnode != NULL && check_inode_type(devnode, device))); + if (strlen(devname) > FS_MAX_DNAME_LEN) { + return -E_TOO_BIG; + } + + int ret = -E_NO_MEM; + char *s_devname; + if ((s_devname = strdup(devname)) == NULL) { + return ret; + } + + vfs_dev_t *vdev; + if ((vdev = kmalloc(sizeof(vfs_dev_t))) == NULL) { + goto failed_cleanup_name; + } + + ret = -E_EXISTS; + lock_vdev_list(); + if (!check_devname_conflict(s_devname)) { + unlock_vdev_list(); + goto failed_cleanup_vdev; + } + vdev->devname = s_devname; + vdev->devnode = devnode; + vdev->mountable = mountable; + vdev->fs = fs; + + list_add(&vdev_list, &(vdev->vdev_link)); + unlock_vdev_list(); + return 0; + +failed_cleanup_vdev: + kfree(vdev); +failed_cleanup_name: + kfree(s_devname); + return ret; +} + +/* + * vfs_add_fs - Add a new fs, by name. See vfs_do_add information for the description of + * mountable. + */ +int +vfs_add_fs(const char *devname, struct fs *fs) { + return vfs_do_add(devname, NULL, fs, 0); +} + +/* + * vfs_add_dev - Add a new device, by name. See vfs_do_add information for the description of + * mountable. + */ +int +vfs_add_dev(const char *devname, struct inode *devnode, bool mountable) { + return vfs_do_add(devname, devnode, NULL, mountable); +} + +/* + * find_mount - Look for a mountable device named DEVNAME. + * Should already hold vdev_list lock. + */ +static int +find_mount(const char *devname, vfs_dev_t **vdev_store) { + assert(devname != NULL); + list_entry_t *list = &vdev_list, *le = list; + while ((le = list_next(le)) != list) { + vfs_dev_t *vdev = le2vdev(le, vdev_link); + if (vdev->mountable && strcmp(vdev->devname, devname) == 0) { + *vdev_store = vdev; + return 0; + } + } + return -E_NO_DEV; +} + +/* + * vfs_mount - Mount a filesystem. Once we've found the device, call MOUNTFUNC to + * set up the filesystem and hand back a struct fs. + * + * The DATA argument is passed through unchanged to MOUNTFUNC. + */ +int +vfs_mount(const char *devname, int (*mountfunc)(struct device *dev, struct fs **fs_store)) { + int ret; + lock_vdev_list(); + vfs_dev_t *vdev; + if ((ret = find_mount(devname, &vdev)) != 0) { + goto out; + } + if (vdev->fs != NULL) { + ret = -E_BUSY; + goto out; + } + assert(vdev->devname != NULL && vdev->mountable); + + struct device *dev = vop_info(vdev->devnode, device); + if ((ret = mountfunc(dev, &(vdev->fs))) == 0) { + assert(vdev->fs != NULL); + cprintf("vfs: mount %s.\n", vdev->devname); + } + +out: + unlock_vdev_list(); + return ret; +} + +/* + * vfs_unmount - Unmount a filesystem/device by name. + * First calls FSOP_SYNC on the filesystem; then calls FSOP_UNMOUNT. + */ +int +vfs_unmount(const char *devname) { + int ret; + lock_vdev_list(); + vfs_dev_t *vdev; + if ((ret = find_mount(devname, &vdev)) != 0) { + goto out; + } + if (vdev->fs == NULL) { + ret = -E_INVAL; + goto out; + } + assert(vdev->devname != NULL && vdev->mountable); + + if ((ret = fsop_sync(vdev->fs)) != 0) { + goto out; + } + if ((ret = fsop_unmount(vdev->fs)) == 0) { + vdev->fs = NULL; + cprintf("vfs: unmount %s.\n", vdev->devname); + } + +out: + unlock_vdev_list(); + return ret; +} + +/* + * vfs_unmount_all - Global unmount function. + */ +int +vfs_unmount_all(void) { + if (!list_empty(&vdev_list)) { + lock_vdev_list(); + { + list_entry_t *list = &vdev_list, *le = list; + while ((le = list_next(le)) != list) { + vfs_dev_t *vdev = le2vdev(le, vdev_link); + if (vdev->mountable && vdev->fs != NULL) { + int ret; + if ((ret = fsop_sync(vdev->fs)) != 0) { + cprintf("vfs: warning: sync failed for %s: %e.\n", vdev->devname, ret); + continue ; + } + if ((ret = fsop_unmount(vdev->fs)) != 0) { + cprintf("vfs: warning: unmount failed for %s: %e.\n", vdev->devname, ret); + continue ; + } + vdev->fs = NULL; + cprintf("vfs: unmount %s.\n", vdev->devname); + } + } + } + unlock_vdev_list(); + } + return 0; +} + diff --git a/code/lab8/kern/fs/vfs/vfsfile.c b/code/lab8/kern/fs/vfs/vfsfile.c new file mode 100644 index 0000000..eb79dca --- /dev/null +++ b/code/lab8/kern/fs/vfs/vfsfile.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include + + +// open file in vfs, get/create inode for file with filename path. +int +vfs_open(char *path, uint32_t open_flags, struct inode **node_store) { + bool can_write = 0; + switch (open_flags & O_ACCMODE) { + case O_RDONLY: + break; + case O_WRONLY: + case O_RDWR: + can_write = 1; + break; + default: + return -E_INVAL; + } + + if (open_flags & O_TRUNC) { + if (!can_write) { + return -E_INVAL; + } + } + + int ret; + struct inode *node; + bool excl = (open_flags & O_EXCL) != 0; + bool create = (open_flags & O_CREAT) != 0; + ret = vfs_lookup(path, &node); + + if (ret != 0) { + if (ret == -16 && (create)) { + char *name; + struct inode *dir; + if ((ret = vfs_lookup_parent(path, &dir, &name)) != 0) { + return ret; + } + ret = vop_create(dir, name, excl, &node); + } else return ret; + } else if (excl && create) { + return -E_EXISTS; + } + assert(node != NULL); + + if ((ret = vop_open(node, open_flags)) != 0) { + vop_ref_dec(node); + return ret; + } + + vop_open_inc(node); + if (open_flags & O_TRUNC || create) { + if ((ret = vop_truncate(node, 0)) != 0) { + vop_open_dec(node); + vop_ref_dec(node); + return ret; + } + } + *node_store = node; + return 0; +} + +// close file in vfs +int +vfs_close(struct inode *node) { + vop_open_dec(node); + vop_ref_dec(node); + return 0; +} + +// unimplement +int +vfs_unlink(char *path) { + return -E_UNIMP; +} + +// unimplement +int +vfs_rename(char *old_path, char *new_path) { + return -E_UNIMP; +} + +// unimplement +int +vfs_link(char *old_path, char *new_path) { + return -E_UNIMP; +} + +// unimplement +int +vfs_symlink(char *old_path, char *new_path) { + return -E_UNIMP; +} + +// unimplement +int +vfs_readlink(char *path, struct iobuf *iob) { + return -E_UNIMP; +} + +// unimplement +int +vfs_mkdir(char *path){ + return -E_UNIMP; +} diff --git a/code/lab8/kern/fs/vfs/vfslookup.c b/code/lab8/kern/fs/vfs/vfslookup.c new file mode 100644 index 0000000..619261d --- /dev/null +++ b/code/lab8/kern/fs/vfs/vfslookup.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include + +/* + * get_device- Common code to pull the device name, if any, off the front of a + * path and choose the inode to begin the name lookup relative to. + */ + +static int +get_device(char *path, char **subpath, struct inode **node_store) { + int i, slash = -1, colon = -1; + for (i = 0; path[i] != '\0'; i ++) { + if (path[i] == ':') { colon = i; break; } + if (path[i] == '/') { slash = i; break; } + } + if (colon < 0 && slash != 0) { + /* * + * No colon before a slash, so no device name specified, and the slash isn't leading + * or is also absent, so this is a relative path or just a bare filename. Start from + * the current directory, and use the whole thing as the subpath. + * */ + *subpath = path; + return vfs_get_curdir(node_store); + } + if (colon > 0) { + /* device:path - get root of device's filesystem */ + path[colon] = '\0'; + + /* device:/path - skip slash, treat as device:path */ + while (path[++ colon] == '/'); + *subpath = path + colon; + return vfs_get_root(path, node_store); + } + + /* * + * we have either /path or :path + * /path is a path relative to the root of the "boot filesystem" + * :path is a path relative to the root of the current filesystem + * */ + int ret; + if (*path == '/') { + if ((ret = vfs_get_bootfs(node_store)) != 0) { + return ret; + } + } + else { + assert(*path == ':'); + struct inode *node; + if ((ret = vfs_get_curdir(&node)) != 0) { + return ret; + } + /* The current directory may not be a device, so it must have a fs. */ + assert(node->in_fs != NULL); + *node_store = fsop_get_root(node->in_fs); + vop_ref_dec(node); + } + + /* ///... or :/... */ + while (*(++ path) == '/'); + *subpath = path; + return 0; +} + +/* + * vfs_lookup - get the inode according to the path filename + */ +int +vfs_lookup(char *path, struct inode **node_store) { + int ret; + struct inode *node; + if ((ret = get_device(path, &path, &node)) != 0) { + return ret; + } + if (*path != '\0') { + ret = vop_lookup(node, path, node_store); + vop_ref_dec(node); + return ret; + } + *node_store = node; + return 0; +} + +/* + * vfs_lookup_parent - Name-to-vnode translation. + * (In BSD, both of these are subsumed by namei().) + */ +int +vfs_lookup_parent(char *path, struct inode **node_store, char **endp){ + int ret; + struct inode *node; + if ((ret = get_device(path, &path, &node)) != 0) { + return ret; + } + *endp = path; + *node_store = node; + return 0; +} diff --git a/code/lab8/kern/fs/vfs/vfspath.c b/code/lab8/kern/fs/vfs/vfspath.c new file mode 100644 index 0000000..0e164df --- /dev/null +++ b/code/lab8/kern/fs/vfs/vfspath.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * get_cwd_nolock - retrieve current process's working directory. without lock protect + */ +static struct inode * +get_cwd_nolock(void) { + return current->filesp->pwd; +} +/* + * set_cwd_nolock - set current working directory. + */ +static void +set_cwd_nolock(struct inode *pwd) { + current->filesp->pwd = pwd; +} + +/* + * lock_cfs - lock the fs related process on current process + */ +static void +lock_cfs(void) { + lock_files(current->filesp); +} +/* + * unlock_cfs - unlock the fs related process on current process + */ +static void +unlock_cfs(void) { + unlock_files(current->filesp); +} + +/* + * vfs_get_curdir - Get current directory as a inode. + */ +int +vfs_get_curdir(struct inode **dir_store) { + struct inode *node; + if ((node = get_cwd_nolock()) != NULL) { + vop_ref_inc(node); + *dir_store = node; + return 0; + } + return -E_NOENT; +} + +/* + * vfs_set_curdir - Set current directory as a inode. + * The passed inode must in fact be a directory. + */ +int +vfs_set_curdir(struct inode *dir) { + int ret = 0; + lock_cfs(); + struct inode *old_dir; + if ((old_dir = get_cwd_nolock()) != dir) { + if (dir != NULL) { + uint32_t type; + if ((ret = vop_gettype(dir, &type)) != 0) { + goto out; + } + if (!S_ISDIR(type)) { + ret = -E_NOTDIR; + goto out; + } + vop_ref_inc(dir); + } + set_cwd_nolock(dir); + if (old_dir != NULL) { + vop_ref_dec(old_dir); + } + } +out: + unlock_cfs(); + return ret; +} + +/* + * vfs_chdir - Set current directory, as a pathname. Use vfs_lookup to translate + * it to a inode. + */ +int +vfs_chdir(char *path) { + int ret; + struct inode *node; + if ((ret = vfs_lookup(path, &node)) == 0) { + ret = vfs_set_curdir(node); + vop_ref_dec(node); + } + return ret; +} +/* + * vfs_getcwd - retrieve current working directory(cwd). + */ +int +vfs_getcwd(struct iobuf *iob) { + int ret; + struct inode *node; + if ((ret = vfs_get_curdir(&node)) != 0) { + return ret; + } + assert(node->in_fs != NULL); + + const char *devname = vfs_get_devname(node->in_fs); + if ((ret = iobuf_move(iob, (char *)devname, strlen(devname), 1, NULL)) != 0) { + goto out; + } + char colon = ':'; + if ((ret = iobuf_move(iob, &colon, sizeof(colon), 1, NULL)) != 0) { + goto out; + } + ret = vop_namefile(node, iob); + +out: + vop_ref_dec(node); + return ret; +} + diff --git a/code/lab8/kern/init/entry.S b/code/lab8/kern/init/entry.S new file mode 100644 index 0000000..8e37f2a --- /dev/null +++ b/code/lab8/kern/init/entry.S @@ -0,0 +1,49 @@ +#include +#include + +#define REALLOC(x) (x - KERNBASE) + +.text +.globl kern_entry +kern_entry: + # reload temperate gdt (second time) to remap all physical memory + # virtual_addr 0~4G=linear_addr&physical_addr -KERNBASE~4G-KERNBASE + lgdt REALLOC(__gdtdesc) + movl $KERNEL_DS, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + + ljmp $KERNEL_CS, $relocated + +relocated: + + # set ebp, esp + movl $0x0, %ebp + # the kernel stack region is from bootstack -- bootstacktop, + # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h + movl $bootstacktop, %esp + # now kernel stack is ready , call the first C function + call kern_init + +# should never get here +spin: + jmp spin + +.data +.align PGSIZE + .globl bootstack +bootstack: + .space KSTACKSIZE + .globl bootstacktop +bootstacktop: + +.align 4 +__gdt: + SEG_NULL + SEG_ASM(STA_X | STA_R, - KERNBASE, 0xFFFFFFFF) # code segment + SEG_ASM(STA_W, - KERNBASE, 0xFFFFFFFF) # data segment +__gdtdesc: + .word 0x17 # sizeof(__gdt) - 1 + .long REALLOC(__gdt) + diff --git a/code/lab8/kern/init/init.c b/code/lab8/kern/init/init.c new file mode 100644 index 0000000..0a95d8b --- /dev/null +++ b/code/lab8/kern/init/init.c @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int kern_init(void) __attribute__((noreturn)); + +static void lab1_switch_test(void); + +int +kern_init(void) { + extern char edata[], end[]; + memset(edata, 0, end - edata); + + cons_init(); // init the console + + const char *message = "(THU.CST) os is loading ..."; + cprintf("%s\n\n", message); + + print_kerninfo(); + + grade_backtrace(); + + pmm_init(); // init physical memory management + + pic_init(); // init interrupt controller + idt_init(); // init interrupt descriptor table + + vmm_init(); // init virtual memory management + sched_init(); // init scheduler + proc_init(); // init process table + + ide_init(); // init ide devices + swap_init(); // init swap + fs_init(); // init fs + + clock_init(); // init clock interrupt + intr_enable(); // enable irq interrupt + + //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() + // user/kernel mode switch test + //lab1_switch_test(); + + cpu_idle(); // run idle process +} + +void __attribute__((noinline)) +grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { + mon_backtrace(0, NULL, NULL); +} + +void __attribute__((noinline)) +grade_backtrace1(int arg0, int arg1) { + grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); +} + +void __attribute__((noinline)) +grade_backtrace0(int arg0, int arg1, int arg2) { + grade_backtrace1(arg0, arg2); +} + +void +grade_backtrace(void) { + grade_backtrace0(0, (int)kern_init, 0xffff0000); +} + +static void +lab1_print_cur_status(void) { + static int round = 0; + uint16_t reg1, reg2, reg3, reg4; + asm volatile ( + "mov %%cs, %0;" + "mov %%ds, %1;" + "mov %%es, %2;" + "mov %%ss, %3;" + : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); + cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: cs = %x\n", round, reg1); + cprintf("%d: ds = %x\n", round, reg2); + cprintf("%d: es = %x\n", round, reg3); + cprintf("%d: ss = %x\n", round, reg4); + round ++; +} + +static void +lab1_switch_to_user(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_to_kernel(void) { + //LAB1 CHALLENGE 1 : TODO +} + +static void +lab1_switch_test(void) { + lab1_print_cur_status(); + cprintf("+++ switch to user mode +++\n"); + lab1_switch_to_user(); + lab1_print_cur_status(); + cprintf("+++ switch to kernel mode +++\n"); + lab1_switch_to_kernel(); + lab1_print_cur_status(); +} + diff --git a/code/lab8/kern/libs/rb_tree.c b/code/lab8/kern/libs/rb_tree.c new file mode 100644 index 0000000..0a5fcc8 --- /dev/null +++ b/code/lab8/kern/libs/rb_tree.c @@ -0,0 +1,528 @@ +#include +#include +#include +#include +#include +#include + +/* rb_node_create - create a new rb_node */ +static inline rb_node * +rb_node_create(void) { + return kmalloc(sizeof(rb_node)); +} + +/* rb_tree_empty - tests if tree is empty */ +static inline bool +rb_tree_empty(rb_tree *tree) { + rb_node *nil = tree->nil, *root = tree->root; + return root->left == nil; +} + +/* * + * rb_tree_create - creates a new red-black tree, the 'compare' function + * is required and returns 'NULL' if failed. + * + * Note that, root->left should always point to the node that is the root + * of the tree. And nil points to a 'NULL' node which should always be + * black and may have arbitrary children and parent node. + * */ +rb_tree * +rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)) { + assert(compare != NULL); + + rb_tree *tree; + rb_node *nil, *root; + + if ((tree = kmalloc(sizeof(rb_tree))) == NULL) { + goto bad_tree; + } + + tree->compare = compare; + + if ((nil = rb_node_create()) == NULL) { + goto bad_node_cleanup_tree; + } + + nil->parent = nil->left = nil->right = nil; + nil->red = 0; + tree->nil = nil; + + if ((root = rb_node_create()) == NULL) { + goto bad_node_cleanup_nil; + } + + root->parent = root->left = root->right = nil; + root->red = 0; + tree->root = root; + return tree; + +bad_node_cleanup_nil: + kfree(nil); +bad_node_cleanup_tree: + kfree(tree); +bad_tree: + return NULL; +} + +/* * + * FUNC_ROTATE - rotates as described in "Introduction to Algorithm". + * + * For example, FUNC_ROTATE(rb_left_rotate, left, right) can be expaned to a + * left-rotate function, which requires an red-black 'tree' and a node 'x' + * to be rotated on. Basically, this function, named rb_left_rotate, makes the + * parent of 'x' be the left child of 'x', 'x' the parent of its parent before + * rotation and finally fixes other nodes accordingly. + * + * FUNC_ROTATE(xx, left, right) means left-rotate, + * and FUNC_ROTATE(xx, right, left) means right-rotate. + * */ +#define FUNC_ROTATE(func_name, _left, _right) \ +static void \ +func_name(rb_tree *tree, rb_node *x) { \ + rb_node *nil = tree->nil, *y = x->_right; \ + assert(x != tree->root && x != nil && y != nil); \ + x->_right = y->_left; \ + if (y->_left != nil) { \ + y->_left->parent = x; \ + } \ + y->parent = x->parent; \ + if (x == x->parent->_left) { \ + x->parent->_left = y; \ + } \ + else { \ + x->parent->_right = y; \ + } \ + y->_left = x; \ + x->parent = y; \ + assert(!(nil->red)); \ +} + +FUNC_ROTATE(rb_left_rotate, left, right); +FUNC_ROTATE(rb_right_rotate, right, left); + +#undef FUNC_ROTATE + +#define COMPARE(tree, node1, node2) \ + ((tree))->compare((node1), (node2)) + +/* * + * rb_insert_binary - insert @node to red-black @tree as if it were + * a regular binary tree. This function is only intended to be called + * by function rb_insert. + * */ +static inline void +rb_insert_binary(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node, *nil = tree->nil, *root = tree->root; + + z->left = z->right = nil; + y = root, x = y->left; + while (x != nil) { + y = x; + x = (COMPARE(tree, x, node) > 0) ? x->left : x->right; + } + z->parent = y; + if (y == root || COMPARE(tree, y, z) > 0) { + y->left = z; + } + else { + y->right = z; + } +} + +/* rb_insert - insert a node to red-black tree */ +void +rb_insert(rb_tree *tree, rb_node *node) { + rb_insert_binary(tree, node); + node->red = 1; + + rb_node *x = node, *y; + +#define RB_INSERT_SUB(_left, _right) \ + do { \ + y = x->parent->parent->_right; \ + if (y->red) { \ + x->parent->red = 0; \ + y->red = 0; \ + x->parent->parent->red = 1; \ + x = x->parent->parent; \ + } \ + else { \ + if (x == x->parent->_right) { \ + x = x->parent; \ + rb_##_left##_rotate(tree, x); \ + } \ + x->parent->red = 0; \ + x->parent->parent->red = 1; \ + rb_##_right##_rotate(tree, x->parent->parent); \ + } \ + } while (0) + + while (x->parent->red) { + if (x->parent == x->parent->parent->left) { + RB_INSERT_SUB(left, right); + } + else { + RB_INSERT_SUB(right, left); + } + } + tree->root->left->red = 0; + assert(!(tree->nil->red) && !(tree->root->red)); + +#undef RB_INSERT_SUB +} + +/* * + * rb_tree_successor - returns the successor of @node, or nil + * if no successor exists. Make sure that @node must belong to @tree, + * and this function should only be called by rb_node_prev. + * */ +static inline rb_node * +rb_tree_successor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->right) != nil) { + while (y->left != nil) { + y = y->left; + } + return y; + } + else { + y = x->parent; + while (x == y->right) { + x = y, y = y->parent; + } + if (y == tree->root) { + return nil; + } + return y; + } +} + +/* * + * rb_tree_predecessor - returns the predecessor of @node, or nil + * if no predecessor exists, likes rb_tree_successor. + * */ +static inline rb_node * +rb_tree_predecessor(rb_tree *tree, rb_node *node) { + rb_node *x = node, *y, *nil = tree->nil; + + if ((y = x->left) != nil) { + while (y->right != nil) { + y = y->right; + } + return y; + } + else { + y = x->parent; + while (x == y->left) { + if (y == tree->root) { + return nil; + } + x = y, y = y->parent; + } + return y; + } +} + +/* * + * rb_search - returns a node with value 'equal' to @key (according to + * function @compare). If there're multiple nodes with value 'equal' to @key, + * the functions returns the one highest in the tree. + * */ +rb_node * +rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key) { + rb_node *nil = tree->nil, *node = tree->root->left; + int r; + while (node != nil && (r = compare(node, key)) != 0) { + node = (r > 0) ? node->left : node->right; + } + return (node != nil) ? node : NULL; +} + +/* * + * rb_delete_fixup - performs rotations and changes colors to restore + * red-black properties after a node is deleted. + * */ +static void +rb_delete_fixup(rb_tree *tree, rb_node *node) { + rb_node *x = node, *w, *root = tree->root->left; + +#define RB_DELETE_FIXUP_SUB(_left, _right) \ + do { \ + w = x->parent->_right; \ + if (w->red) { \ + w->red = 0; \ + x->parent->red = 1; \ + rb_##_left##_rotate(tree, x->parent); \ + w = x->parent->_right; \ + } \ + if (!w->_left->red && !w->_right->red) { \ + w->red = 1; \ + x = x->parent; \ + } \ + else { \ + if (!w->_right->red) { \ + w->_left->red = 0; \ + w->red = 1; \ + rb_##_right##_rotate(tree, w); \ + w = x->parent->_right; \ + } \ + w->red = x->parent->red; \ + x->parent->red = 0; \ + w->_right->red = 0; \ + rb_##_left##_rotate(tree, x->parent); \ + x = root; \ + } \ + } while (0) + + while (x != root && !x->red) { + if (x == x->parent->left) { + RB_DELETE_FIXUP_SUB(left, right); + } + else { + RB_DELETE_FIXUP_SUB(right, left); + } + } + x->red = 0; + +#undef RB_DELETE_FIXUP_SUB +} + +/* * + * rb_delete - deletes @node from @tree, and calls rb_delete_fixup to + * restore red-black properties. + * */ +void +rb_delete(rb_tree *tree, rb_node *node) { + rb_node *x, *y, *z = node; + rb_node *nil = tree->nil, *root = tree->root; + + y = (z->left == nil || z->right == nil) ? z : rb_tree_successor(tree, z); + x = (y->left != nil) ? y->left : y->right; + + assert(y != root && y != nil); + + x->parent = y->parent; + if (y == y->parent->left) { + y->parent->left = x; + } + else { + y->parent->right = x; + } + + bool need_fixup = !(y->red); + + if (y != z) { + if (z == z->parent->left) { + z->parent->left = y; + } + else { + z->parent->right = y; + } + z->left->parent = z->right->parent = y; + *y = *z; + } + if (need_fixup) { + rb_delete_fixup(tree, x); + } +} + +/* rb_tree_destroy - destroy a tree and free memory */ +void +rb_tree_destroy(rb_tree *tree) { + kfree(tree->root); + kfree(tree->nil); + kfree(tree); +} + +/* * + * rb_node_prev - returns the predecessor node of @node in @tree, + * or 'NULL' if no predecessor exists. + * */ +rb_node * +rb_node_prev(rb_tree *tree, rb_node *node) { + rb_node *prev = rb_tree_predecessor(tree, node); + return (prev != tree->nil) ? prev : NULL; +} + +/* * + * rb_node_next - returns the successor node of @node in @tree, + * or 'NULL' if no successor exists. + * */ +rb_node * +rb_node_next(rb_tree *tree, rb_node *node) { + rb_node *next = rb_tree_successor(tree, node); + return (next != tree->nil) ? next : NULL; +} + +/* rb_node_root - returns the root node of a @tree, or 'NULL' if tree is empty */ +rb_node * +rb_node_root(rb_tree *tree) { + rb_node *node = tree->root->left; + return (node != tree->nil) ? node : NULL; +} + +/* rb_node_left - gets the left child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_left(rb_tree *tree, rb_node *node) { + rb_node *left = node->left; + return (left != tree->nil) ? left : NULL; +} + +/* rb_node_right - gets the right child of @node, or 'NULL' if no such node */ +rb_node * +rb_node_right(rb_tree *tree, rb_node *node) { + rb_node *right = node->right; + return (right != tree->nil) ? right : NULL; +} + +int +check_tree(rb_tree *tree, rb_node *node) { + rb_node *nil = tree->nil; + if (node == nil) { + assert(!node->red); + return 1; + } + if (node->left != nil) { + assert(COMPARE(tree, node, node->left) >= 0); + assert(node->left->parent == node); + } + if (node->right != nil) { + assert(COMPARE(tree, node, node->right) <= 0); + assert(node->right->parent == node); + } + if (node->red) { + assert(!node->left->red && !node->right->red); + } + int hb_left = check_tree(tree, node->left); + int hb_right = check_tree(tree, node->right); + assert(hb_left == hb_right); + int hb = hb_left; + if (!node->red) { + hb ++; + } + return hb; +} + +static void * +check_safe_kmalloc(size_t size) { + void *ret = kmalloc(size); + assert(ret != NULL); + return ret; +} + +struct check_data { + long data; + rb_node rb_link; +}; + +#define rbn2data(node) \ + (to_struct(node, struct check_data, rb_link)) + +static inline int +check_compare1(rb_node *node1, rb_node *node2) { + return rbn2data(node1)->data - rbn2data(node2)->data; +} + +static inline int +check_compare2(rb_node *node, void *key) { + return rbn2data(node)->data - (long)key; +} + +void +check_rb_tree(void) { + rb_tree *tree = rb_tree_create(check_compare1); + assert(tree != NULL); + + rb_node *nil = tree->nil, *root = tree->root; + assert(!nil->red && root->left == nil); + + int total = 1000; + struct check_data **all = check_safe_kmalloc(sizeof(struct check_data *) * total); + + long i; + for (i = 0; i < total; i ++) { + all[i] = check_safe_kmalloc(sizeof(struct check_data)); + all[i]->data = i; + } + + int *mark = check_safe_kmalloc(sizeof(int) * total); + memset(mark, 0, sizeof(int) * total); + + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + int j = (rand() % (total - i)) + i; + struct check_data *z = all[i]; + all[i] = all[j]; + all[j] = z; + } + + memset(mark, 0, sizeof(int) * total); + for (i = 0; i < total; i ++) { + mark[all[i]->data] = 1; + } + for (i = 0; i < total; i ++) { + assert(mark[i] == 1); + } + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_node *node; + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)(all[i]->data)); + assert(node != NULL && node == &(all[i]->rb_link)); + } + + for (i = 0; i < total; i ++) { + node = rb_search(tree, check_compare2, (void *)i); + assert(node != NULL && rbn2data(node)->data == i); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(!nil->red && root->left == nil); + + long max = 32; + if (max > total) { + max = total; + } + + for (i = 0; i < max; i ++) { + all[i]->data = max; + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + for (i = 0; i < max; i ++) { + node = rb_search(tree, check_compare2, (void *)max); + assert(node != NULL && rbn2data(node)->data == max); + rb_delete(tree, node); + check_tree(tree, root->left); + } + + assert(rb_tree_empty(tree)); + + for (i = 0; i < total; i ++) { + rb_insert(tree, &(all[i]->rb_link)); + check_tree(tree, root->left); + } + + rb_tree_destroy(tree); + + for (i = 0; i < total; i ++) { + kfree(all[i]); + } + + kfree(mark); + kfree(all); +} + diff --git a/code/lab8/kern/libs/rb_tree.h b/code/lab8/kern/libs/rb_tree.h new file mode 100644 index 0000000..a2aa9aa --- /dev/null +++ b/code/lab8/kern/libs/rb_tree.h @@ -0,0 +1,32 @@ +#ifndef __KERN_LIBS_RB_TREE_H__ +#define __KERN_LIBS_RB_TREE_H__ + +#include + +typedef struct rb_node { + bool red; // if red = 0, it's a black node + struct rb_node *parent; + struct rb_node *left, *right; +} rb_node; + +typedef struct rb_tree { + // compare function should return -1 if *node1 < *node2, 1 if *node1 > *node2, and 0 otherwise + int (*compare)(rb_node *node1, rb_node *node2); + struct rb_node *nil, *root; +} rb_tree; + +rb_tree *rb_tree_create(int (*compare)(rb_node *node1, rb_node *node2)); +void rb_tree_destroy(rb_tree *tree); +void rb_insert(rb_tree *tree, rb_node *node); +void rb_delete(rb_tree *tree, rb_node *node); +rb_node *rb_search(rb_tree *tree, int (*compare)(rb_node *node, void *key), void *key); +rb_node *rb_node_prev(rb_tree *tree, rb_node *node); +rb_node *rb_node_next(rb_tree *tree, rb_node *node); +rb_node *rb_node_root(rb_tree *tree); +rb_node *rb_node_left(rb_tree *tree, rb_node *node); +rb_node *rb_node_right(rb_tree *tree, rb_node *node); + +void check_rb_tree(void); + +#endif /* !__KERN_LIBS_RBTREE_H__ */ + diff --git a/code/lab8/kern/libs/readline.c b/code/lab8/kern/libs/readline.c new file mode 100644 index 0000000..cc1eddb --- /dev/null +++ b/code/lab8/kern/libs/readline.c @@ -0,0 +1,50 @@ +#include + +#define BUFSIZE 1024 +static char buf[BUFSIZE]; + +/* * + * readline - get a line from stdin + * @prompt: the string to be written to stdout + * + * The readline() function will write the input string @prompt to + * stdout first. If the @prompt is NULL or the empty string, + * no prompt is issued. + * + * This function will keep on reading characters and saving them to buffer + * 'buf' until '\n' or '\r' is encountered. + * + * Note that, if the length of string that will be read is longer than + * buffer size, the end of string will be discarded. + * + * The readline() function returns the text of the line read. If some errors + * are happened, NULL is returned. The return value is a global variable, + * thus it should be copied before it is used. + * */ +char * +readline(const char *prompt) { + if (prompt != NULL) { + cprintf("%s", prompt); + } + int i = 0, c; + while (1) { + c = getchar(); + if (c < 0) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + cputchar(c); + buf[i ++] = c; + } + else if (c == '\b' && i > 0) { + cputchar(c); + i --; + } + else if (c == '\n' || c == '\r') { + cputchar(c); + buf[i] = '\0'; + return buf; + } + } +} + diff --git a/code/lab8/kern/libs/stdio.c b/code/lab8/kern/libs/stdio.c new file mode 100644 index 0000000..aaade41 --- /dev/null +++ b/code/lab8/kern/libs/stdio.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +/* HIGH level console I/O */ + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + cons_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, NO_FD, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vcprintf(fmt, ap); + va_end(ap); + return cnt; +} + +/* cputchar - writes a single character to stdout */ +void +cputchar(int c) { + cons_putc(c); +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + +/* getchar - reads a single non-zero character from stdin */ +int +getchar(void) { + int c; + while ((c = cons_getc()) == 0) + /* do nothing */; + return c; +} + diff --git a/code/lab8/kern/libs/string.c b/code/lab8/kern/libs/string.c new file mode 100644 index 0000000..4b3548e --- /dev/null +++ b/code/lab8/kern/libs/string.c @@ -0,0 +1,26 @@ +#include +#include + +char * +strdup(const char *src) { + char *dst; + size_t len = strlen(src); + if ((dst = kmalloc(len + 1)) != NULL) { + memcpy(dst, src, len); + dst[len] = '\0'; + } + return dst; +} + +char * +stradd(const char *src1, const char *src2) { + char *ret, *dst; + size_t len1 = strlen(src1), len2 = strlen(src2); + if ((ret = dst = kmalloc(len1 + len2 + 1)) != NULL) { + memcpy(dst, src1, len1), dst += len1; + memcpy(dst, src2, len2), dst += len2; + *dst = '\0'; + } + return ret; +} + diff --git a/code/lab8/kern/mm/default_pmm.c b/code/lab8/kern/mm/default_pmm.c new file mode 100644 index 0000000..770a30f --- /dev/null +++ b/code/lab8/kern/mm/default_pmm.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include + +/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and, + on receiving a request for memory, scans along the list for the first block that is large enough to + satisfy the request. If the chosen block is significantly larger than that requested, then it is + usually split, and the remainder added to the list as another free block. + Please see Page 196~198, Section 8.2 of Yan Wei Ming's chinese book "Data Structure -- C programming language" +*/ +// LAB2 EXERCISE 1: YOUR CODE +// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages. +/* + * Details of FFMA + * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list. + * The struct free_area_t is used for the management of free mem blocks. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation. + * You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev + * Another tricky method is to transform a general list struct to a special struct (such as struct page): + * you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.) + * (2) default_init: you can reuse the demo default_init fun to init the free_list and set nr_free to 0. + * free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks. + * (3) default_init_memmap: CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap + * This fun is used to init a free block (with parameter: addr_base, page_number). + * First you should init each page (in memlayout.h) in this free block, include: + * p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c), + * the bit PG_reserved is setted in p->flags) + * if this page is free and is not the first page of free block, p->property should be set to 0. + * if this page is free and is the first page of free block, p->property should be set to total num of block. + * p->ref should be 0, because now p is free and no reference. + * We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); ) + * Finally, we should sum the number of free mem block: nr_free+=n + * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr + * of malloced block. + * (4.1) So you should search freelist like this: + * list_entry_t le = &free_list; + * while((le=list_next(le)) != &free_list) { + * .... + * (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n? + * struct Page *p = le2page(le, page_link); + * if(p->property >= n){ ... + * (4.1.2) If we find this p, then it' means we find a free block(block size >=n), and the first n pages can be malloced. + * Some flag bits of this page should be setted: PG_reserved =1, PG_property =0 + * unlink the pages from free_list + * (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block, + * (such as: le2page(le,page_link))->property = p->property - n;) + * (4.1.3) re-caluclate nr_free (number of the the rest of all free block) + * (4.1.4) return p + * (4.2) If we can not find a free block (block size >=n), then return NULL + * (5) default_free_pages: relink the pages into free list, maybe merge small free blocks into big free blocks. + * (5.1) according the base addr of withdrawed blocks, search free list, find the correct position + * (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before) + * (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty) + * (5.3) try to merge low addr or high addr blocks. Notice: should change some pages's p->property correctly. + */ +free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +default_init(void) { + list_init(&free_list); + nr_free = 0; +} + +static void +default_init_memmap(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(PageReserved(p)); + p->flags = p->property = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static struct Page * +default_alloc_pages(size_t n) { + assert(n > 0); + if (n > nr_free) { + return NULL; + } + struct Page *page = NULL; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + if (p->property >= n) { + page = p; + break; + } + } + if (page != NULL) { + list_del(&(page->page_link)); + if (page->property > n) { + struct Page *p = page + n; + p->property = page->property - n; + list_add(&free_list, &(p->page_link)); + } + nr_free -= n; + ClearPageProperty(page); + } + return page; +} + +static void +default_free_pages(struct Page *base, size_t n) { + assert(n > 0); + struct Page *p = base; + for (; p != base + n; p ++) { + assert(!PageReserved(p) && !PageProperty(p)); + p->flags = 0; + set_page_ref(p, 0); + } + base->property = n; + SetPageProperty(base); + list_entry_t *le = list_next(&free_list); + while (le != &free_list) { + p = le2page(le, page_link); + le = list_next(le); + if (base + base->property == p) { + base->property += p->property; + ClearPageProperty(p); + list_del(&(p->page_link)); + } + else if (p + p->property == base) { + p->property += base->property; + ClearPageProperty(base); + base = p; + list_del(&(p->page_link)); + } + } + nr_free += n; + list_add(&free_list, &(base->page_link)); +} + +static size_t +default_nr_free_pages(void) { + return nr_free; +} + +static void +basic_check(void) { + struct Page *p0, *p1, *p2; + p0 = p1 = p2 = NULL; + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(p0 != p1 && p0 != p2 && p1 != p2); + assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); + + assert(page2pa(p0) < npage * PGSIZE); + assert(page2pa(p1) < npage * PGSIZE); + assert(page2pa(p2) < npage * PGSIZE); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + assert(alloc_page() == NULL); + + free_page(p0); + free_page(p1); + free_page(p2); + assert(nr_free == 3); + + assert((p0 = alloc_page()) != NULL); + assert((p1 = alloc_page()) != NULL); + assert((p2 = alloc_page()) != NULL); + + assert(alloc_page() == NULL); + + free_page(p0); + assert(!list_empty(&free_list)); + + struct Page *p; + assert((p = alloc_page()) == p0); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + free_list = free_list_store; + nr_free = nr_free_store; + + free_page(p); + free_page(p1); + free_page(p2); +} + +// LAB2: below code is used to check the first fit allocation algorithm (your EXERCISE 1) +// NOTICE: You SHOULD NOT CHANGE basic_check, default_check functions! +static void +default_check(void) { + int count = 0, total = 0; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + + basic_check(); + + struct Page *p0 = alloc_pages(5), *p1, *p2; + assert(p0 != NULL); + assert(!PageProperty(p0)); + + list_entry_t free_list_store = free_list; + list_init(&free_list); + assert(list_empty(&free_list)); + assert(alloc_page() == NULL); + + unsigned int nr_free_store = nr_free; + nr_free = 0; + + free_pages(p0 + 2, 3); + assert(alloc_pages(4) == NULL); + assert(PageProperty(p0 + 2) && p0[2].property == 3); + assert((p1 = alloc_pages(3)) != NULL); + assert(alloc_page() == NULL); + assert(p0 + 2 == p1); + + p2 = p0 + 1; + free_page(p0); + free_pages(p1, 3); + assert(PageProperty(p0) && p0->property == 1); + assert(PageProperty(p1) && p1->property == 3); + + assert((p0 = alloc_page()) == p2 - 1); + free_page(p0); + assert((p0 = alloc_pages(2)) == p2 + 1); + + free_pages(p0, 2); + free_page(p2); + + assert((p0 = alloc_pages(5)) != NULL); + assert(alloc_page() == NULL); + + assert(nr_free == 0); + nr_free = nr_free_store; + + free_list = free_list_store; + free_pages(p0, 5); + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + assert(count == 0); + assert(total == 0); +} + +const struct pmm_manager default_pmm_manager = { + .name = "default_pmm_manager", + .init = default_init, + .init_memmap = default_init_memmap, + .alloc_pages = default_alloc_pages, + .free_pages = default_free_pages, + .nr_free_pages = default_nr_free_pages, + .check = default_check, +}; + diff --git a/code/lab8/kern/mm/default_pmm.h b/code/lab8/kern/mm/default_pmm.h new file mode 100644 index 0000000..5f4e6fc --- /dev/null +++ b/code/lab8/kern/mm/default_pmm.h @@ -0,0 +1,9 @@ +#ifndef __KERN_MM_DEFAULT_PMM_H__ +#define __KERN_MM_DEFAULT_PMM_H__ + +#include + +extern const struct pmm_manager default_pmm_manager; +extern free_area_t free_area; +#endif /* ! __KERN_MM_DEFAULT_PMM_H__ */ + diff --git a/code/lab8/kern/mm/kmalloc.c b/code/lab8/kern/mm/kmalloc.c new file mode 100644 index 0000000..aa5bb90 --- /dev/null +++ b/code/lab8/kern/mm/kmalloc.c @@ -0,0 +1,640 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* The slab allocator used in ucore is based on an algorithm first introduced by + Jeff Bonwick for the SunOS operating system. The paper can be download from + http://citeseer.ist.psu.edu/bonwick94slab.html + An implementation of the Slab Allocator as described in outline in; + UNIX Internals: The New Frontiers by Uresh Vahalia + Pub: Prentice Hall ISBN 0-13-101908-2 + Within a kernel, a considerable amount of memory is allocated for a finite set + of objects such as file descriptors and other common structures. Jeff found that + the amount of time required to initialize a regular object in the kernel exceeded + the amount of time required to allocate and deallocate it. His conclusion was + that instead of freeing the memory back to a global pool, he would have the memory + remain initialized for its intended purpose. + In our simple slab implementation, the the high-level organization of the slab + structures is simplied. At the highest level is an array slab_cache[SLAB_CACHE_NUM], + and each array element is a slab_cache which has slab chains. Each slab_cache has + two list, one list chains the full allocated slab, and another list chains the notfull + allocated(maybe empty) slab. And each slab has fixed number(2^n) of pages. In each + slab, there are a lot of objects (such as ) with same fixed size(32B ~ 128KB). + + +----------------------------------+ + | slab_cache[0] for 0~32B obj | + +----------------------------------+ + | slab_cache[1] for 33B~64B obj |-->lists for slabs + +----------------------------------+ | + | slab_cache[2] for 65B~128B obj | | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | + +----------------------------------+ | + | slab_cache[12]for 64KB~128KB obj | | + +----------------------------------+ | + | + slabs_full/slabs_not +---------------------+ + -<-----------<----------<-+ + | | | + slab1 slab2 slab3... + | + |-------|-------| + pages1 pages2 pages3... + | + | + | + slab_t+n*bufctl_t+obj1-obj2-obj3...objn (the size of obj is small) + | + OR + | + obj1-obj2-obj3...objn WITH slab_t+n*bufctl_t in another slab (the size of obj is BIG) + + The important functions are: + kmem_cache_grow(kmem_cache_t *cachep) + kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) + kmalloc(size_t size): used by outside functions need dynamicly get memory + kfree(void *objp): used by outside functions need dynamicly release memory +*/ + +#define BUFCTL_END 0xFFFFFFFFL // the signature of the last bufctl +#define SLAB_LIMIT 0xFFFFFFFEL // the max value of obj number + +typedef size_t kmem_bufctl_t; //the index of obj in slab + +typedef struct slab_s { + list_entry_t slab_link; // the list entry linked to kmem_cache list + void *s_mem; // the kernel virtual address of the first obj in slab + size_t inuse; // the number of allocated objs + size_t offset; // the first obj's offset value in slab + kmem_bufctl_t free; // the first free obj's index in slab +} slab_t; + +// get the slab address according to the link element (see list.h) +#define le2slab(le, member) \ + to_struct((le), slab_t, member) + +typedef struct kmem_cache_s kmem_cache_t; + + +struct kmem_cache_s { + list_entry_t slabs_full; // list for fully allocated slabs + list_entry_t slabs_notfull; // list for not-fully allocated slabs + + size_t objsize; // the fixed size of obj + size_t num; // number of objs per slab + size_t offset; // this first obj's offset in slab + bool off_slab; // the control part of slab in slab or not. + + /* order of pages per slab (2^n) */ + size_t page_order; + + kmem_cache_t *slab_cachep; +}; + +#define MIN_SIZE_ORDER 5 // 32 +#define MAX_SIZE_ORDER 17 // 128k +#define SLAB_CACHE_NUM (MAX_SIZE_ORDER - MIN_SIZE_ORDER + 1) + +static kmem_cache_t slab_cache[SLAB_CACHE_NUM]; + +static void init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align); +static void check_slab(void); + + +//slab_init - call init_kmem_cache function to reset the slab_cache array +static void +slab_init(void) { + size_t i; + //the align bit for obj in slab. 2^n could be better for performance + size_t align = 16; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + init_kmem_cache(slab_cache + i, 1 << (i + MIN_SIZE_ORDER), align); + } + check_slab(); +} + +inline void +kmalloc_init(void) { + slab_init(); + cprintf("kmalloc_init() succeeded!\n"); +} + +//slab_allocated - summary the total size of allocated objs +static size_t +slab_allocated(void) { + size_t total = 0; + int i; + bool intr_flag; + local_intr_save(intr_flag); + { + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + list_entry_t *list, *le; + list = le = &(cachep->slabs_full); + while ((le = list_next(le)) != list) { + total += cachep->num * cachep->objsize; + } + list = le = &(cachep->slabs_notfull); + while ((le = list_next(le)) != list) { + slab_t *slabp = le2slab(le, slab_link); + total += slabp->inuse * cachep->objsize; + } + } + } + local_intr_restore(intr_flag); + return total; +} + +inline size_t +kallocated(void) { + return slab_allocated(); +} + +// slab_mgmt_size - get the size of slab control area (slab_t+num*kmem_bufctl_t) +static size_t +slab_mgmt_size(size_t num, size_t align) { + return ROUNDUP(sizeof(slab_t) + num * sizeof(kmem_bufctl_t), align); +} + +// cacahe_estimate - estimate the number of objs in a slab +static void +cache_estimate(size_t order, size_t objsize, size_t align, bool off_slab, size_t *remainder, size_t *num) { + size_t nr_objs, mgmt_size; + size_t slab_size = (PGSIZE << order); + + if (off_slab) { + mgmt_size = 0; + nr_objs = slab_size / objsize; + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + } + else { + nr_objs = (slab_size - sizeof(slab_t)) / (objsize + sizeof(kmem_bufctl_t)); + while (slab_mgmt_size(nr_objs, align) + nr_objs * objsize > slab_size) { + nr_objs --; + } + if (nr_objs > SLAB_LIMIT) { + nr_objs = SLAB_LIMIT; + } + mgmt_size = slab_mgmt_size(nr_objs, align); + } + *num = nr_objs; + *remainder = slab_size - nr_objs * objsize - mgmt_size; +} + +// calculate_slab_order - estimate the size(4K~4M) of slab +// paramemters: +// cachep: the slab_cache +// objsize: the size of obj +// align: align bit for objs +// off_slab: the control part of slab in slab or not +// left_over: the size of can not be used area in slab +static void +calculate_slab_order(kmem_cache_t *cachep, size_t objsize, size_t align, bool off_slab, size_t *left_over) { + size_t order; + for (order = 0; order <= KMALLOC_MAX_ORDER; order ++) { + size_t num, remainder; + cache_estimate(order, objsize, align, off_slab, &remainder, &num); + if (num != 0) { + if (off_slab) { + size_t off_slab_limit = objsize - sizeof(slab_t); + off_slab_limit /= sizeof(kmem_bufctl_t); + if (num > off_slab_limit) { + panic("off_slab: objsize = %d, num = %d.", objsize, num); + } + } + if (remainder * 8 <= (PGSIZE << order)) { + cachep->num = num; + cachep->page_order = order; + if (left_over != NULL) { + *left_over = remainder; + } + return ; + } + } + } + panic("calculate_slab_over: failed."); +} + +// getorder - find order, should satisfy n <= minest 2^order +static inline size_t +getorder(size_t n) { + size_t order = MIN_SIZE_ORDER, order_size = (1 << order); + for (; order <= MAX_SIZE_ORDER; order ++, order_size <<= 1) { + if (n <= order_size) { + return order; + } + } + panic("getorder failed. %d\n", n); +} + +// init_kmem_cache - initial a slab_cache cachep according to the obj with the size = objsize +static void +init_kmem_cache(kmem_cache_t *cachep, size_t objsize, size_t align) { + list_init(&(cachep->slabs_full)); + list_init(&(cachep->slabs_notfull)); + + objsize = ROUNDUP(objsize, align); + cachep->objsize = objsize; + cachep->off_slab = (objsize >= (PGSIZE >> 3)); + + size_t left_over; + calculate_slab_order(cachep, objsize, align, cachep->off_slab, &left_over); + + assert(cachep->num > 0); + + size_t mgmt_size = slab_mgmt_size(cachep->num, align); + + if (cachep->off_slab && left_over >= mgmt_size) { + cachep->off_slab = 0; + } + + if (cachep->off_slab) { + cachep->offset = 0; + cachep->slab_cachep = slab_cache + (getorder(mgmt_size) - MIN_SIZE_ORDER); + } + else { + cachep->offset = mgmt_size; + } +} + +static void *kmem_cache_alloc(kmem_cache_t *cachep); + +#define slab_bufctl(slabp) \ + ((kmem_bufctl_t*)(((slab_t *)(slabp)) + 1)) + +// kmem_cache_slabmgmt - get the address of a slab according to page +// - and initialize the slab according to cachep +static slab_t * +kmem_cache_slabmgmt(kmem_cache_t *cachep, struct Page *page) { + void *objp = page2kva(page); + slab_t *slabp; + if (cachep->off_slab) { + if ((slabp = kmem_cache_alloc(cachep->slab_cachep)) == NULL) { + return NULL; + } + } + else { + slabp = page2kva(page); + } + slabp->inuse = 0; + slabp->offset = cachep->offset; + slabp->s_mem = objp + cachep->offset; + return slabp; +} + +#define SET_PAGE_CACHE(page, cachep) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + kmem_cache_t **__cachepp = (kmem_cache_t **)&(__page->page_link.next); \ + *__cachepp = (kmem_cache_t *)(cachep); \ + } while (0) + +#define SET_PAGE_SLAB(page, slabp) \ + do { \ + struct Page *__page = (struct Page *)(page); \ + slab_t **__cachepp = (slab_t **)&(__page->page_link.prev); \ + *__cachepp = (slab_t *)(slabp); \ + } while (0) + +// kmem_cache_grow - allocate a new slab by calling alloc_pages +// - set control area in the new slab +static bool +kmem_cache_grow(kmem_cache_t *cachep) { + struct Page *page = alloc_pages(1 << cachep->page_order); + if (page == NULL) { + goto failed; + } + + slab_t *slabp; + if ((slabp = kmem_cache_slabmgmt(cachep, page)) == NULL) { + goto oops; + } + + size_t order_size = (1 << cachep->page_order); + do { + //setup this page in the free list (see memlayout.h: struct page)??? + SET_PAGE_CACHE(page, cachep); + SET_PAGE_SLAB(page, slabp); + //this page is used for slab + SetPageSlab(page); + page ++; + } while (-- order_size); + + int i; + for (i = 0; i < cachep->num; i ++) { + slab_bufctl(slabp)[i] = i + 1; + } + slab_bufctl(slabp)[cachep->num - 1] = BUFCTL_END; + slabp->free = 0; + + bool intr_flag; + local_intr_save(intr_flag); + { + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } + local_intr_restore(intr_flag); + return 1; + +oops: + free_pages(page, 1 << cachep->page_order); +failed: + return 0; +} + +// kmem_cache_alloc_one - allocate a obj in a slab +static void * +kmem_cache_alloc_one(kmem_cache_t *cachep, slab_t *slabp) { + slabp->inuse ++; + void *objp = slabp->s_mem + slabp->free * cachep->objsize; + slabp->free = slab_bufctl(slabp)[slabp->free]; + + if (slabp->free == BUFCTL_END) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_full), &(slabp->slab_link)); + } + return objp; +} + +// kmem_cache_alloc - call kmem_cache_alloc_one function to allocate a obj +// - if no free obj, try to allocate a slab +static void * +kmem_cache_alloc(kmem_cache_t *cachep) { + void *objp; + bool intr_flag; + +try_again: + local_intr_save(intr_flag); + if (list_empty(&(cachep->slabs_notfull))) { + goto alloc_new_slab; + } + slab_t *slabp = le2slab(list_next(&(cachep->slabs_notfull)), slab_link); + objp = kmem_cache_alloc_one(cachep, slabp); + local_intr_restore(intr_flag); + return objp; + +alloc_new_slab: + local_intr_restore(intr_flag); + + if (kmem_cache_grow(cachep)) { + goto try_again; + } + return NULL; +} + +// kmalloc - simple interface used by outside functions +// - to allocate a free memory using kmem_cache_alloc function +void * +kmalloc(size_t size) { + assert(size > 0); + size_t order = getorder(size); + if (order > MAX_SIZE_ORDER) { + return NULL; + } + return kmem_cache_alloc(slab_cache + (order - MIN_SIZE_ORDER)); +} + +static void kmem_cache_free(kmem_cache_t *cachep, void *obj); + +// kmem_slab_destroy - call free_pages & kmem_cache_free to free a slab +static void +kmem_slab_destroy(kmem_cache_t *cachep, slab_t *slabp) { + struct Page *page = kva2page(slabp->s_mem - slabp->offset); + + struct Page *p = page; + size_t order_size = (1 << cachep->page_order); + do { + assert(PageSlab(p)); + ClearPageSlab(p); + p ++; + } while (-- order_size); + + free_pages(page, 1 << cachep->page_order); + + if (cachep->off_slab) { + kmem_cache_free(cachep->slab_cachep, slabp); + } +} + +// kmem_cache_free_one - free an obj in a slab +// - if slab->inuse==0, then free the slab +static void +kmem_cache_free_one(kmem_cache_t *cachep, slab_t *slabp, void *objp) { + //should not use divide operator ??? + size_t objnr = (objp - slabp->s_mem) / cachep->objsize; + slab_bufctl(slabp)[objnr] = slabp->free; + slabp->free = objnr; + + slabp->inuse --; + + if (slabp->inuse == 0) { + list_del(&(slabp->slab_link)); + kmem_slab_destroy(cachep, slabp); + } + else if (slabp->inuse == cachep->num -1 ) { + list_del(&(slabp->slab_link)); + list_add(&(cachep->slabs_notfull), &(slabp->slab_link)); + } +} + +#define GET_PAGE_CACHE(page) \ + (kmem_cache_t *)((page)->page_link.next) + +#define GET_PAGE_SLAB(page) \ + (slab_t *)((page)->page_link.prev) + +// kmem_cache_free - call kmem_cache_free_one function to free an obj +static void +kmem_cache_free(kmem_cache_t *cachep, void *objp) { + bool intr_flag; + struct Page *page = kva2page(objp); + + if (!PageSlab(page)) { + panic("not a slab page %08x\n", objp); + } + local_intr_save(intr_flag); + { + kmem_cache_free_one(cachep, GET_PAGE_SLAB(page), objp); + } + local_intr_restore(intr_flag); +} + +// kfree - simple interface used by ooutside functions to free an obj +void +kfree(void *objp) { + kmem_cache_free(GET_PAGE_CACHE(kva2page(objp)), objp); +} + +static inline void +check_slab_empty(void) { + int i; + for (i = 0; i < SLAB_CACHE_NUM; i ++) { + kmem_cache_t *cachep = slab_cache + i; + assert(list_empty(&(cachep->slabs_full))); + assert(list_empty(&(cachep->slabs_notfull))); + } +} + +void +check_slab(void) { + int i; + void *v0, *v1; + + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = slab_allocated(); + + /* slab must be empty now */ + check_slab_empty(); + assert(slab_allocated() == 0); + + kmem_cache_t *cachep0, *cachep1; + + cachep0 = slab_cache; + assert(cachep0->objsize == 32 && cachep0->num > 1 && !cachep0->off_slab); + assert((v0 = kmalloc(16)) != NULL); + + slab_t *slabp0, *slabp1; + + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + assert(slabp0->inuse == 1 && list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + struct Page *p0, *p1; + size_t order_size; + + + p0 = kva2page(slabp0->s_mem - slabp0->offset), p1 = p0; + order_size = (1 << cachep0->page_order); + for (i = 0; i < cachep0->page_order; i ++, p1 ++) { + assert(PageSlab(p1)); + assert(GET_PAGE_CACHE(p1) == cachep0 && GET_PAGE_SLAB(p1) == slabp0); + } + + assert(v0 == slabp0->s_mem); + assert((v1 = kmalloc(16)) != NULL && v1 == v0 + 32); + + kfree(v0); + assert(slabp0->free == 0); + kfree(v1); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->page_order; i ++, p0 ++) { + assert(!PageSlab(p0)); + } + + + v0 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + for (i = 0; i < cachep0->num - 1; i ++) { + kmalloc(16); + } + + assert(slabp0->inuse == cachep0->num); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + v1 = kmalloc(16); + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + + kfree(v0); + assert(list_empty(&(cachep0->slabs_full))); + assert(list_next(&(slabp0->slab_link)) == &(slabp1->slab_link) + || list_next(&(slabp1->slab_link)) == &(slabp0->slab_link)); + + kfree(v1); + assert(!list_empty(&(cachep0->slabs_notfull))); + assert(list_next(&(cachep0->slabs_notfull)) == &(slabp0->slab_link)); + assert(list_next(&(slabp0->slab_link)) == &(cachep0->slabs_notfull)); + + v1 = kmalloc(16); + assert(v1 == v0); + assert(list_next(&(cachep0->slabs_full)) == &(slabp0->slab_link)); + assert(list_empty(&(cachep0->slabs_notfull))); + + for (i = 0; i < cachep0->num; i ++) { + kfree(v1 + i * cachep0->objsize); + } + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + cachep0 = slab_cache; + + bool has_off_slab = 0; + for (i = 0; i < SLAB_CACHE_NUM; i ++, cachep0 ++) { + if (cachep0->off_slab) { + has_off_slab = 1; + cachep1 = cachep0->slab_cachep; + if (!cachep1->off_slab) { + break; + } + } + } + + if (!has_off_slab) { + goto check_pass; + } + + assert(cachep0->off_slab && !cachep1->off_slab); + assert(cachep1 < cachep0); + + assert(list_empty(&(cachep0->slabs_full))); + assert(list_empty(&(cachep0->slabs_notfull))); + + assert(list_empty(&(cachep1->slabs_full))); + assert(list_empty(&(cachep1->slabs_notfull))); + + v0 = kmalloc(cachep0->objsize); + p0 = kva2page(v0); + assert(page2kva(p0) == v0); + + if (cachep0->num == 1) { + assert(!list_empty(&(cachep0->slabs_full))); + slabp0 = le2slab(list_next(&(cachep0->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep0->slabs_notfull))); + slabp0 = le2slab(list_next(&(cachep0->slabs_notfull)), slab_link); + } + + assert(slabp0 != NULL); + + if (cachep1->num == 1) { + assert(!list_empty(&(cachep1->slabs_full))); + slabp1 = le2slab(list_next(&(cachep1->slabs_full)), slab_link); + } + else { + assert(!list_empty(&(cachep1->slabs_notfull))); + slabp1 = le2slab(list_next(&(cachep1->slabs_notfull)), slab_link); + } + + assert(slabp1 != NULL); + + order_size = (1 << cachep0->page_order); + for (i = 0; i < order_size; i ++, p0 ++) { + assert(PageSlab(p0)); + assert(GET_PAGE_CACHE(p0) == cachep0 && GET_PAGE_SLAB(p0) == slabp0); + } + + kfree(v0); + +check_pass: + + check_rb_tree(); + check_slab_empty(); + assert(slab_allocated() == 0); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == slab_allocated()); + + cprintf("check_slab() succeeded!\n"); +} + diff --git a/code/lab8/kern/mm/kmalloc.h b/code/lab8/kern/mm/kmalloc.h new file mode 100644 index 0000000..8617975 --- /dev/null +++ b/code/lab8/kern/mm/kmalloc.h @@ -0,0 +1,16 @@ +#ifndef __KERN_MM_SLAB_H__ +#define __KERN_MM_SLAB_H__ + +#include + +#define KMALLOC_MAX_ORDER 10 + +void kmalloc_init(void); + +void *kmalloc(size_t n); +void kfree(void *objp); + +size_t kallocated(void); + +#endif /* !__KERN_MM_SLAB_H__ */ + diff --git a/code/lab8/kern/mm/memlayout.h b/code/lab8/kern/mm/memlayout.h new file mode 100644 index 0000000..d84bf93 --- /dev/null +++ b/code/lab8/kern/mm/memlayout.h @@ -0,0 +1,169 @@ +#ifndef __KERN_MM_MEMLAYOUT_H__ +#define __KERN_MM_MEMLAYOUT_H__ + +/* This file contains the definitions for memory management in our OS. */ + +/* global segment number */ +#define SEG_KTEXT 1 +#define SEG_KDATA 2 +#define SEG_UTEXT 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +/* global descrptor numbers */ +#define GD_KTEXT ((SEG_KTEXT) << 3) // kernel text +#define GD_KDATA ((SEG_KDATA) << 3) // kernel data +#define GD_UTEXT ((SEG_UTEXT) << 3) // user text +#define GD_UDATA ((SEG_UDATA) << 3) // user data +#define GD_TSS ((SEG_TSS) << 3) // task segment selector + +#define DPL_KERNEL (0) +#define DPL_USER (3) + +#define KERNEL_CS ((GD_KTEXT) | DPL_KERNEL) +#define KERNEL_DS ((GD_KDATA) | DPL_KERNEL) +#define USER_CS ((GD_UTEXT) | DPL_USER) +#define USER_DS ((GD_UDATA) | DPL_USER) + +/* * + * Virtual memory map: Permissions + * kernel/user + * + * 4G ------------------> +---------------------------------+ + * | | + * | Empty Memory (*) | + * | | + * +---------------------------------+ 0xFB000000 + * | Cur. Page Table (Kern, RW) | RW/-- PTSIZE + * VPT -----------------> +---------------------------------+ 0xFAC00000 + * | Invalid Memory (*) | --/-- + * KERNTOP -------------> +---------------------------------+ 0xF8000000 + * | | + * | Remapped Physical Memory | RW/-- KMEMSIZE + * | | + * KERNBASE ------------> +---------------------------------+ 0xC0000000 + * | Invalid Memory (*) | --/-- + * USERTOP -------------> +---------------------------------+ 0xB0000000 + * | User stack | + * +---------------------------------+ + * | | + * : : + * | ~~~~~~~~~~~~~~~~ | + * : : + * | | + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * | User Program & Heap | + * UTEXT ---------------> +---------------------------------+ 0x00800000 + * | Invalid Memory (*) | --/-- + * | - - - - - - - - - - - - - - - | + * | User STAB Data (optional) | + * USERBASE, USTAB------> +---------------------------------+ 0x00200000 + * | Invalid Memory (*) | --/-- + * 0 -------------------> +---------------------------------+ 0x00000000 + * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped. + * "Empty Memory" is normally unmapped, but user programs may map pages + * there if desired. + * + * */ + +/* All physical memory mapped at this address */ +#define KERNBASE 0xC0000000 +#define KMEMSIZE 0x38000000 // the maximum amount of physical memory +#define KERNTOP (KERNBASE + KMEMSIZE) + +/* * + * Virtual page table. Entry PDX[VPT] in the PD (Page Directory) contains + * a pointer to the page directory itself, thereby turning the PD into a page + * table, which maps all the PTEs (Page Table Entry) containing the page mappings + * for the entire virtual address space into that 4 Meg region starting at VPT. + * */ +#define VPT 0xFAC00000 + +#define KSTACKPAGE 2 // # of pages in kernel stack +#define KSTACKSIZE (KSTACKPAGE * PGSIZE) // sizeof kernel stack + +#define USERTOP 0xB0000000 +#define USTACKTOP USERTOP +#define USTACKPAGE 256 // # of pages in user stack +#define USTACKSIZE (USTACKPAGE * PGSIZE) // sizeof user stack + +#define USERBASE 0x00200000 +#define UTEXT 0x00800000 // where user programs generally begin +#define USTAB USERBASE // the location of the user STABS data structure + +#define USER_ACCESS(start, end) \ +(USERBASE <= (start) && (start) < (end) && (end) <= USERTOP) + +#define KERN_ACCESS(start, end) \ +(KERNBASE <= (start) && (start) < (end) && (end) <= KERNTOP) + +#ifndef __ASSEMBLER__ + +#include +#include +#include + +typedef uintptr_t pte_t; +typedef uintptr_t pde_t; +typedef pte_t swap_entry_t; //the pte can also be a swap entry + +// some constants for bios interrupt 15h AX = 0xE820 +#define E820MAX 20 // number of entries in E820MAP +#define E820_ARM 1 // address range memory +#define E820_ARR 2 // address range reserved + +struct e820map { + int nr_map; + struct { + uint64_t addr; + uint64_t size; + uint32_t type; + } __attribute__((packed)) map[E820MAX]; +}; + +/* * + * struct Page - Page descriptor structures. Each Page describes one + * physical page. In kern/mm/pmm.h, you can find lots of useful functions + * that convert Page to other data types, such as phyical address. + * */ +struct Page { + atomic_t ref; // page frame's reference counter + uint32_t flags; // array of flags that describe the status of the page frame + unsigned int property; // used in buddy system, stores the order (the X in 2^X) of the continuous memory block + int zone_num; // used in buddy system, the No. of zone which the page belongs to + list_entry_t page_link; // free list link + list_entry_t pra_page_link; // used for pra (page replace algorithm) + uintptr_t pra_vaddr; // used for pra (page replace algorithm) +}; + +/* Flags describing the status of a page frame */ +#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable +#define PG_property 1 // the member 'property' is valid + +#define SetPageReserved(page) set_bit(PG_reserved, &((page)->flags)) +#define ClearPageReserved(page) clear_bit(PG_reserved, &((page)->flags)) +#define PageReserved(page) test_bit(PG_reserved, &((page)->flags)) +#define SetPageProperty(page) set_bit(PG_property, &((page)->flags)) +#define ClearPageProperty(page) clear_bit(PG_property, &((page)->flags)) +#define PageProperty(page) test_bit(PG_property, &((page)->flags)) + +// convert list entry to page +#define le2page(le, member) \ + to_struct((le), struct Page, member) + +/* free_area_t - maintains a doubly linked list to record free (unused) pages */ +typedef struct { + list_entry_t free_list; // the list header + unsigned int nr_free; // # of free pages in this free list +} free_area_t; + +/* for slab style kmalloc */ +#define PG_slab 2 // page frame is included in a slab +#define SetPageSlab(page) set_bit(PG_slab, &((page)->flags)) +#define ClearPageSlab(page) clear_bit(PG_slab, &((page)->flags)) +#define PageSlab(page) test_bit(PG_slab, &((page)->flags)) + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__KERN_MM_MEMLAYOUT_H__ */ + diff --git a/code/lab8/kern/mm/mmu.h b/code/lab8/kern/mm/mmu.h new file mode 100644 index 0000000..3858ad9 --- /dev/null +++ b/code/lab8/kern/mm/mmu.h @@ -0,0 +1,272 @@ +#ifndef __KERN_MM_MMU_H__ +#define __KERN_MM_MMU_H__ + +/* Eflags register */ +#define FL_CF 0x00000001 // Carry Flag +#define FL_PF 0x00000004 // Parity Flag +#define FL_AF 0x00000010 // Auxiliary carry Flag +#define FL_ZF 0x00000040 // Zero Flag +#define FL_SF 0x00000080 // Sign Flag +#define FL_TF 0x00000100 // Trap Flag +#define FL_IF 0x00000200 // Interrupt Flag +#define FL_DF 0x00000400 // Direction Flag +#define FL_OF 0x00000800 // Overflow Flag +#define FL_IOPL_MASK 0x00003000 // I/O Privilege Level bitmask +#define FL_IOPL_0 0x00000000 // IOPL == 0 +#define FL_IOPL_1 0x00001000 // IOPL == 1 +#define FL_IOPL_2 0x00002000 // IOPL == 2 +#define FL_IOPL_3 0x00003000 // IOPL == 3 +#define FL_NT 0x00004000 // Nested Task +#define FL_RF 0x00010000 // Resume Flag +#define FL_VM 0x00020000 // Virtual 8086 mode +#define FL_AC 0x00040000 // Alignment Check +#define FL_VIF 0x00080000 // Virtual Interrupt Flag +#define FL_VIP 0x00100000 // Virtual Interrupt Pending +#define FL_ID 0x00200000 // ID flag + +/* Application segment type bits */ +#define STA_X 0x8 // Executable segment +#define STA_E 0x4 // Expand down (non-executable segments) +#define STA_C 0x4 // Conforming code segment (executable only) +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) +#define STA_A 0x1 // Accessed + +/* System segment type bits */ +#define STS_T16A 0x1 // Available 16-bit TSS +#define STS_LDT 0x2 // Local Descriptor Table +#define STS_T16B 0x3 // Busy 16-bit TSS +#define STS_CG16 0x4 // 16-bit Call Gate +#define STS_TG 0x5 // Task Gate / Coum Transmitions +#define STS_IG16 0x6 // 16-bit Interrupt Gate +#define STS_TG16 0x7 // 16-bit Trap Gate +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_T32B 0xB // Busy 32-bit TSS +#define STS_CG32 0xC // 32-bit Call Gate +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#ifdef __ASSEMBLER__ + +#define SEG_NULL \ + .word 0, 0; \ + .byte 0, 0, 0, 0 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#else /* not __ASSEMBLER__ */ + +#include + +/* Gate descriptors for interrupts and traps */ +struct gatedesc { + unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment + unsigned gd_ss : 16; // segment selector + unsigned gd_args : 5; // # args, 0 for interrupt/trap gates + unsigned gd_rsv1 : 3; // reserved(should be zero I guess) + unsigned gd_type : 4; // type(STS_{TG,IG32,TG32}) + unsigned gd_s : 1; // must be 0 (system) + unsigned gd_dpl : 2; // descriptor(meaning new) privilege level + unsigned gd_p : 1; // Present + unsigned gd_off_31_16 : 16; // high bits of offset in segment +}; + +/* * + * Set up a normal interrupt/trap gate descriptor + * - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate + * - sel: Code segment selector for interrupt/trap handler + * - off: Offset in code segment for interrupt/trap handler + * - dpl: Descriptor Privilege Level - the privilege level required + * for software to invoke this interrupt/trap gate explicitly + * using an int instruction. + * */ +#define SETGATE(gate, istrap, sel, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (sel); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* Set up a call gate descriptor */ +#define SETCALLGATE(gate, ss, off, dpl) { \ + (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \ + (gate).gd_ss = (ss); \ + (gate).gd_args = 0; \ + (gate).gd_rsv1 = 0; \ + (gate).gd_type = STS_CG32; \ + (gate).gd_s = 0; \ + (gate).gd_dpl = (dpl); \ + (gate).gd_p = 1; \ + (gate).gd_off_31_16 = (uint32_t)(off) >> 16; \ + } + +/* segment descriptors */ +struct segdesc { + unsigned sd_lim_15_0 : 16; // low bits of segment limit + unsigned sd_base_15_0 : 16; // low bits of segment base address + unsigned sd_base_23_16 : 8; // middle bits of segment base address + unsigned sd_type : 4; // segment type (see STS_ constants) + unsigned sd_s : 1; // 0 = system, 1 = application + unsigned sd_dpl : 2; // descriptor Privilege Level + unsigned sd_p : 1; // present + unsigned sd_lim_19_16 : 4; // high bits of segment limit + unsigned sd_avl : 1; // unused (available for software use) + unsigned sd_rsv1 : 1; // reserved + unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment + unsigned sd_g : 1; // granularity: limit scaled by 4K when set + unsigned sd_base_31_24 : 8; // high bits of segment base address +}; + +#define SEG_NULL \ + (struct segdesc) {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +#define SEG(type, base, lim, dpl) \ + (struct segdesc) { \ + ((lim) >> 12) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 1, dpl, 1, \ + (unsigned)(lim) >> 28, 0, 0, 1, 1, \ + (unsigned) (base) >> 24 \ + } + +#define SEGTSS(type, base, lim, dpl) \ + (struct segdesc) { \ + (lim) & 0xffff, (base) & 0xffff, \ + ((base) >> 16) & 0xff, type, 0, dpl, 1, \ + (unsigned) (lim) >> 16, 0, 0, 1, 0, \ + (unsigned) (base) >> 24 \ + } + +/* task state segment format (as described by the Pentium architecture book) */ +struct taskstate { + uint32_t ts_link; // old ts selector + uintptr_t ts_esp0; // stack pointers and segment selectors + uint16_t ts_ss0; // after an increase in privilege level + uint16_t ts_padding1; + uintptr_t ts_esp1; + uint16_t ts_ss1; + uint16_t ts_padding2; + uintptr_t ts_esp2; + uint16_t ts_ss2; + uint16_t ts_padding3; + uintptr_t ts_cr3; // page directory base + uintptr_t ts_eip; // saved state from last task switch + uint32_t ts_eflags; + uint32_t ts_eax; // more saved state (registers) + uint32_t ts_ecx; + uint32_t ts_edx; + uint32_t ts_ebx; + uintptr_t ts_esp; + uintptr_t ts_ebp; + uint32_t ts_esi; + uint32_t ts_edi; + uint16_t ts_es; // even more saved state (segment selectors) + uint16_t ts_padding4; + uint16_t ts_cs; + uint16_t ts_padding5; + uint16_t ts_ss; + uint16_t ts_padding6; + uint16_t ts_ds; + uint16_t ts_padding7; + uint16_t ts_fs; + uint16_t ts_padding8; + uint16_t ts_gs; + uint16_t ts_padding9; + uint16_t ts_ldt; + uint16_t ts_padding10; + uint16_t ts_t; // trap on task switch + uint16_t ts_iomb; // i/o map base address +} __attribute__((packed)); + +#endif /* !__ASSEMBLER__ */ + +// A linear address 'la' has a three-part structure as follows: +// +// +--------10------+-------10-------+---------12----------+ +// | Page Directory | Page Table | Offset within Page | +// | Index | Index | | +// +----------------+----------------+---------------------+ +// \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/ +// \----------- PPN(la) -----------/ +// +// The PDX, PTX, PGOFF, and PPN macros decompose linear addresses as shown. +// To construct a linear address la from PDX(la), PTX(la), and PGOFF(la), +// use PGADDR(PDX(la), PTX(la), PGOFF(la)). + +// page directory index +#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) + +// page table index +#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) + +// page number field of address +#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT) + +// offset in page +#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF) + +// construct linear address from indexes and offset +#define PGADDR(d, t, o) ((uintptr_t)((d) << PDXSHIFT | (t) << PTXSHIFT | (o))) + +// address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#define PDE_ADDR(pde) PTE_ADDR(pde) + +/* page directory and page table constants */ +#define NPDEENTRY 1024 // page directory entries per page directory +#define NPTEENTRY 1024 // page table entries per page table + +#define PGSIZE 4096 // bytes mapped by a page +#define PGSHIFT 12 // log2(PGSIZE) +#define PTSIZE (PGSIZE * NPTEENTRY) // bytes mapped by a page directory entry +#define PTSHIFT 22 // log2(PTSIZE) + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +/* page table/directory entry flags */ +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size +#define PTE_MBZ 0x180 // Bits must be zero +#define PTE_AVAIL 0xE00 // Available for software use + // The PTE_AVAIL bits aren't used by the kernel or interpreted by the + // hardware, so user processes are allowed to set them arbitrarily. + +#define PTE_USER (PTE_U | PTE_W | PTE_P) + +/* Control Register flags */ +#define CR0_PE 0x00000001 // Protection Enable +#define CR0_MP 0x00000002 // Monitor coProcessor +#define CR0_EM 0x00000004 // Emulation +#define CR0_TS 0x00000008 // Task Switched +#define CR0_ET 0x00000010 // Extension Type +#define CR0_NE 0x00000020 // Numeric Errror +#define CR0_WP 0x00010000 // Write Protect +#define CR0_AM 0x00040000 // Alignment Mask +#define CR0_NW 0x20000000 // Not Writethrough +#define CR0_CD 0x40000000 // Cache Disable +#define CR0_PG 0x80000000 // Paging + +#define CR4_PCE 0x00000100 // Performance counter enable +#define CR4_MCE 0x00000040 // Machine Check Enable +#define CR4_PSE 0x00000010 // Page Size Extensions +#define CR4_DE 0x00000008 // Debugging Extensions +#define CR4_TSD 0x00000004 // Time Stamp Disable +#define CR4_PVI 0x00000002 // Protected-Mode Virtual Interrupts +#define CR4_VME 0x00000001 // V86 Mode Extensions + +#endif /* !__KERN_MM_MMU_H__ */ + diff --git a/code/lab8/kern/mm/pmm.c b/code/lab8/kern/mm/pmm.c new file mode 100644 index 0000000..cc3f28c --- /dev/null +++ b/code/lab8/kern/mm/pmm.c @@ -0,0 +1,759 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* * + * Task State Segment: + * + * The TSS may reside anywhere in memory. A special segment register called + * the Task Register (TR) holds a segment selector that points a valid TSS + * segment descriptor which resides in the GDT. Therefore, to use a TSS + * the following must be done in function gdt_init: + * - create a TSS descriptor entry in GDT + * - add enough information to the TSS in memory as needed + * - load the TR register with a segment selector for that segment + * + * There are several fileds in TSS for specifying the new stack pointer when a + * privilege level change happens. But only the fields SS0 and ESP0 are useful + * in our os kernel. + * + * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 + * contains the new ESP value for CPL = 0. When an interrupt happens in protected + * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value + * into SS and ESP respectively. + * */ +static struct taskstate ts = {0}; + +// virtual address of physicall page array +struct Page *pages; +// amount of physical memory (in pages) +size_t npage = 0; + +// virtual address of boot-time page directory +pde_t *boot_pgdir = NULL; +// physical address of boot-time page directory +uintptr_t boot_cr3; + +// physical memory management +const struct pmm_manager *pmm_manager; + +/* * + * The page directory entry corresponding to the virtual address range + * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page + * directory is treated as a page table as well as a page directory. + * + * One result of treating the page directory as a page table is that all PTEs + * can be accessed though a "virtual page table" at virtual address VPT. And the + * PTE for number n is stored in vpt[n]. + * + * A second consequence is that the contents of the current page directory will + * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which + * vpd is set bellow. + * */ +pte_t * const vpt = (pte_t *)VPT; +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); + +/* * + * Global Descriptor Table: + * + * The kernel and user segments are identical (except for the DPL). To load + * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the + * segments for the user and the kernel. Defined as follows: + * - 0x0 : unused (always faults -- for trapping NULL far pointers) + * - 0x8 : kernel code segment + * - 0x10: kernel data segment + * - 0x18: user code segment + * - 0x20: user data segment + * - 0x28: defined for tss, initialized in gdt_init + * */ +static struct segdesc gdt[] = { + SEG_NULL, + [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), + [SEG_TSS] = SEG_NULL, +}; + +static struct pseudodesc gdt_pd = { + sizeof(gdt) - 1, (uintptr_t)gdt +}; + +static void check_alloc_page(void); +static void check_pgdir(void); +static void check_boot_pgdir(void); + +/* * + * lgdt - load the global descriptor table register and reset the + * data/code segement registers for kernel. + * */ +static inline void +lgdt(struct pseudodesc *pd) { + asm volatile ("lgdt (%0)" :: "r" (pd)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + // reload cs + asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); +} + +/* * + * load_esp0 - change the ESP0 in default task state segment, + * so that we can use different kernel stack when we trap frame + * user to kernel. + * */ +void +load_esp0(uintptr_t esp0) { + ts.ts_esp0 = esp0; +} + +/* gdt_init - initialize the default GDT and TSS */ +static void +gdt_init(void) { + // set boot kernel stack and default SS0 + load_esp0((uintptr_t)bootstacktop); + ts.ts_ss0 = KERNEL_DS; + + // initialize the TSS filed of the gdt + gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); + + // reload all segment registers + lgdt(&gdt_pd); + + // load the TSS + ltr(GD_TSS); +} + +//init_pmm_manager - initialize a pmm_manager instance +static void +init_pmm_manager(void) { + pmm_manager = &default_pmm_manager; + cprintf("memory management: %s\n", pmm_manager->name); + pmm_manager->init(); +} + +//init_memmap - call pmm->init_memmap to build Page struct for free memory +static void +init_memmap(struct Page *base, size_t n) { + pmm_manager->init_memmap(base, n); +} + +//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +struct Page * +alloc_pages(size_t n) { + struct Page *page=NULL; + bool intr_flag; + + while (1) + { + local_intr_save(intr_flag); + { + page = pmm_manager->alloc_pages(n); + } + local_intr_restore(intr_flag); + + if (page != NULL || n > 1 || swap_init_ok == 0) break; + + extern struct mm_struct *check_mm_struct; + //cprintf("page %x, call swap_out in alloc_pages %d\n",page, n); + swap_out(check_mm_struct, n, 0); + } + //cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages)); + return page; +} + +//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +void +free_pages(struct Page *base, size_t n) { + bool intr_flag; + local_intr_save(intr_flag); + { + pmm_manager->free_pages(base, n); + } + local_intr_restore(intr_flag); +} + +//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) +//of current free memory +size_t +nr_free_pages(void) { + size_t ret; + bool intr_flag; + local_intr_save(intr_flag); + { + ret = pmm_manager->nr_free_pages(); + } + local_intr_restore(intr_flag); + return ret; +} + +/* pmm_init - initialize the physical memory management */ +static void +page_init(void) { + struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); + uint64_t maxpa = 0; + + cprintf("e820map:\n"); + int i; + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + memmap->map[i].size, begin, end - 1, memmap->map[i].type); + if (memmap->map[i].type == E820_ARM) { + if (maxpa < end && begin < KMEMSIZE) { + maxpa = end; + } + } + } + if (maxpa > KMEMSIZE) { + maxpa = KMEMSIZE; + } + + extern char end[]; + + npage = maxpa / PGSIZE; + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + + for (i = 0; i < npage; i ++) { + SetPageReserved(pages + i); + } + + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + + for (i = 0; i < memmap->nr_map; i ++) { + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; + if (memmap->map[i].type == E820_ARM) { + if (begin < freemem) { + begin = freemem; + } + if (end > KMEMSIZE) { + end = KMEMSIZE; + } + if (begin < end) { + begin = ROUNDUP(begin, PGSIZE); + end = ROUNDDOWN(end, PGSIZE); + if (begin < end) { + init_memmap(pa2page(begin), (end - begin) / PGSIZE); + } + } + } + } +} + +static void +enable_paging(void) { + lcr3(boot_cr3); + + // turn on paging + uint32_t cr0 = rcr0(); + cr0 |= CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP; + cr0 &= ~(CR0_TS | CR0_EM); + lcr0(cr0); +} + +//boot_map_segment - setup&enable the paging mechanism +// parameters +// la: linear address of this memory need to map (after x86 segment map) +// size: memory size +// pa: physical address of this memory +// perm: permission of this memory +static void +boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + assert(PGOFF(la) == PGOFF(pa)); + size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + la = ROUNDDOWN(la, PGSIZE); + pa = ROUNDDOWN(pa, PGSIZE); + for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + pte_t *ptep = get_pte(pgdir, la, 1); + assert(ptep != NULL); + *ptep = pa | PTE_P | perm; + } +} + +//boot_alloc_page - allocate one page using pmm->alloc_pages(1) +// return value: the kernel virtual address of this allocated page +//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +static void * +boot_alloc_page(void) { + struct Page *p = alloc_page(); + if (p == NULL) { + panic("boot_alloc_page failed.\n"); + } + return page2kva(p); +} + +//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism +// - check the correctness of pmm & paging mechanism, print PDT&PT +void +pmm_init(void) { + //We need to alloc/free the physical memory (granularity is 4KB or other size). + //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h + //First we should init a physical memory manager(pmm) based on the framework. + //Then pmm can alloc/free the physical memory. + //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available. + init_pmm_manager(); + + // detect physical memory space, reserve already used memory, + // then use pmm->init_memmap to create free page list + page_init(); + + //use pmm->check to verify the correctness of the alloc/free function in a pmm + check_alloc_page(); + + // create boot_pgdir, an initial page directory(Page Directory Table, PDT) + boot_pgdir = boot_alloc_page(); + memset(boot_pgdir, 0, PGSIZE); + boot_cr3 = PADDR(boot_pgdir); + + check_pgdir(); + + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + + // recursively insert boot_pgdir in itself + // to form a virtual page table at virtual address VPT + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + + // map all physical memory to linear memory with base linear addr KERNBASE + //linear_addr KERNBASE~KERNBASE+KMEMSIZE = phy_addr 0~KMEMSIZE + //But shouldn't use this map until enable_paging() & gdt_init() finished. + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + + //temporary map: + //virtual_addr 3G~3G+4M = linear_addr 0~4M = linear_addr 3G~3G+4M = phy_addr 0~4M + boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)]; + + enable_paging(); + + //reload gdt(third time,the last time) to map all physical memory + //virtual_addr 0~4G=liear_addr 0~4G + //then set kernel stack(ss:esp) in TSS, setup TSS in gdt, load TSS + gdt_init(); + + //disable the map of virtual_addr 0~4M + boot_pgdir[0] = 0; + + //now the basic virtual memory map(see memalyout.h) is established. + //check the correctness of the basic virtual memory map. + check_boot_pgdir(); + + print_pgdir(); + + kmalloc_init(); + +} + +//get_pte - get pte and return the kernel virtual address of this pte for la +// - if the PT contians this pte didn't exist, alloc a page for PT +// parameter: +// pgdir: the kernel virtual base address of PDT +// la: the linear address need to map +// create: a logical value to decide if alloc a page for PT +// return vaule: the kernel virtual address of this pte +pte_t * +get_pte(pde_t *pgdir, uintptr_t la, bool create) { + /* LAB2 EXERCISE 2: YOUR CODE + * + * If you need to visit a physical address, please use KADDR() + * please read pmm.h for useful macros + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la. + * KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address. + * set_page_ref(page,1) : means the page be referenced by one time + * page2pa(page): get the physical address of memory which this (struct Page *) page manages + * struct Page * alloc_page() : allocation a page + * memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s + * to the specified value c. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + */ +#if 0 + pde_t *pdep = NULL; // (1) find page directory entry + if (0) { // (2) check if entry is not present + // (3) check if creating is needed, then alloc page for page table + // CAUTION: this page is used for page table, not for common data page + // (4) set page reference + uintptr_t pa = 0; // (5) get linear address of page + // (6) clear page content using memset + // (7) set page directory entry's permission + } + return NULL; // (8) return page table entry +#endif +} + +//get_page - get related Page struct for linear address la using PDT pgdir +struct Page * +get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep_store != NULL) { + *ptep_store = ptep; + } + if (ptep != NULL && *ptep & PTE_P) { + return pa2page(*ptep); + } + return NULL; +} + +//page_remove_pte - free an Page sturct which is related linear address la +// - and clean(invalidate) pte which is related linear address la +//note: PT is changed, so the TLB need to be invalidate +static inline void +page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { + /* LAB2 EXERCISE 3: YOUR CODE + * + * Please check if ptep is valid, and tlb must be manually updated if mapping is updated + * + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * struct Page *page pte2page(*ptep): get the according page from the value of a ptep + * free_page : free a page + * page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free. + * tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being + * edited are the ones currently in use by the processor. + * DEFINEs: + * PTE_P 0x001 // page table/directory entry flags bit : Present + */ +#if 0 + if (0) { //(1) check if page directory is present + struct Page *page = NULL; //(2) find corresponding page to pte + //(3) decrease page reference + //(4) and free this page when page reference reachs 0 + //(5) clear second page table entry + //(6) flush tlb + } +#endif +} + +void +unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + do { + pte_t *ptep = get_pte(pgdir, start, 0); + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + if (*ptep != 0) { + page_remove_pte(pgdir, start, ptep); + } + start += PGSIZE; + } while (start != 0 && start < end); +} + +void +exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + + start = ROUNDDOWN(start, PTSIZE); + do { + int pde_idx = PDX(start); + if (pgdir[pde_idx] & PTE_P) { + free_page(pde2page(pgdir[pde_idx])); + pgdir[pde_idx] = 0; + } + start += PTSIZE; + } while (start != 0 && start < end); +} +/* copy_range - copy content of memory (start, end) of one process A to another process B + * @to: the addr of process B's Page Directory + * @from: the addr of process A's Page Directory + * @share: flags to indicate to dup OR share. We just use dup method, so it didn't be used. + * + * CALL GRAPH: copy_mm-->dup_mmap-->copy_range + */ +int +copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share) { + assert(start % PGSIZE == 0 && end % PGSIZE == 0); + assert(USER_ACCESS(start, end)); + // copy content by page unit. + do { + //call get_pte to find process A's pte according to the addr start + pte_t *ptep = get_pte(from, start, 0), *nptep; + if (ptep == NULL) { + start = ROUNDDOWN(start + PTSIZE, PTSIZE); + continue ; + } + //call get_pte to find process B's pte according to the addr start. If pte is NULL, just alloc a PT + if (*ptep & PTE_P) { + if ((nptep = get_pte(to, start, 1)) == NULL) { + return -E_NO_MEM; + } + uint32_t perm = (*ptep & PTE_USER); + //get page from ptep + struct Page *page = pte2page(*ptep); + // alloc a page for process B + struct Page *npage=alloc_page(); + assert(page!=NULL); + assert(npage!=NULL); + int ret=0; + /* LAB5:EXERCISE2 YOUR CODE + * replicate content of page to npage, build the map of phy addr of nage with the linear addr start + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * page2kva(struct Page *page): return the kernel vritual addr of memory which page managed (SEE pmm.h) + * page_insert: build the map of phy addr of an Page with the linear addr la + * memcpy: typical memory copy function + * + * (1) find src_kvaddr: the kernel virtual address of page + * (2) find dst_kvaddr: the kernel virtual address of npage + * (3) memory copy from src_kvaddr to dst_kvaddr, size is PGSIZE + * (4) build the map of phy addr of nage with the linear addr start + */ + assert(ret == 0); + } + start += PGSIZE; + } while (start != 0 && start < end); + return 0; +} + +//page_remove - free an Page which is related linear address la and has an validated pte +void +page_remove(pde_t *pgdir, uintptr_t la) { + pte_t *ptep = get_pte(pgdir, la, 0); + if (ptep != NULL) { + page_remove_pte(pgdir, la, ptep); + } +} + +//page_insert - build the map of phy addr of an Page with the linear addr la +// paramemters: +// pgdir: the kernel virtual base address of PDT +// page: the Page which need to map +// la: the linear address need to map +// perm: the permission of this Page which is setted in related pte +// return value: always 0 +//note: PT is changed, so the TLB need to be invalidate +int +page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + pte_t *ptep = get_pte(pgdir, la, 1); + if (ptep == NULL) { + return -E_NO_MEM; + } + page_ref_inc(page); + if (*ptep & PTE_P) { + struct Page *p = pte2page(*ptep); + if (p == page) { + page_ref_dec(page); + } + else { + page_remove_pte(pgdir, la, ptep); + } + } + *ptep = page2pa(page) | PTE_P | perm; + tlb_invalidate(pgdir, la); + return 0; +} + +// invalidate a TLB entry, but only if the page tables being +// edited are the ones currently in use by the processor. +void +tlb_invalidate(pde_t *pgdir, uintptr_t la) { + if (rcr3() == PADDR(pgdir)) { + invlpg((void *)la); + } +} + +// pgdir_alloc_page - call alloc_page & page_insert functions to +// - allocate a page size memory & setup an addr map +// - pa<->la with linear address la and the PDT pgdir +struct Page * +pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { + struct Page *page = alloc_page(); + if (page != NULL) { + if (page_insert(pgdir, page, la, perm) != 0) { + free_page(page); + return NULL; + } + if (swap_init_ok){ + if(check_mm_struct!=NULL) { + swap_map_swappable(check_mm_struct, la, page, 0); + page->pra_vaddr=la; + assert(page_ref(page) == 1); + //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); + } + else { //now current is existed, should fix it in the future + //swap_map_swappable(current->mm, la, page, 0); + //page->pra_vaddr=la; + //assert(page_ref(page) == 1); + //panic("pgdir_alloc_page: no pages. now current is existed, should fix it in the future\n"); + } + } + + } + + return page; +} + +static void +check_alloc_page(void) { + pmm_manager->check(); + cprintf("check_alloc_page() succeeded!\n"); +} + +static void +check_pgdir(void) { + assert(npage <= KMEMSIZE / PGSIZE); + assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + assert(get_page(boot_pgdir, 0x0, NULL) == NULL); + + struct Page *p1, *p2; + p1 = alloc_page(); + assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + + pte_t *ptep; + assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert(page_ref(p1) == 1); + + ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; + assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); + + p2 = alloc_page(); + assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(*ptep & PTE_U); + assert(*ptep & PTE_W); + assert(boot_pgdir[0] & PTE_U); + assert(page_ref(p2) == 1); + + assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + assert(page_ref(p1) == 2); + assert(page_ref(p2) == 0); + assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + assert(pa2page(*ptep) == p1); + assert((*ptep & PTE_U) == 0); + + page_remove(boot_pgdir, 0x0); + assert(page_ref(p1) == 1); + assert(page_ref(p2) == 0); + + page_remove(boot_pgdir, PGSIZE); + assert(page_ref(p1) == 0); + assert(page_ref(p2) == 0); + + assert(page_ref(pa2page(boot_pgdir[0])) == 1); + free_page(pa2page(boot_pgdir[0])); + boot_pgdir[0] = 0; + + cprintf("check_pgdir() succeeded!\n"); +} + +static void +check_boot_pgdir(void) { + pte_t *ptep; + int i; + for (i = 0; i < npage; i += PGSIZE) { + assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + assert(PTE_ADDR(*ptep) == i); + } + + assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); + + assert(boot_pgdir[0] == 0); + + struct Page *p; + p = alloc_page(); + assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); + assert(page_ref(p) == 1); + assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); + assert(page_ref(p) == 2); + + const char *str = "ucore: Hello world!!"; + strcpy((void *)0x100, str); + assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); + + *(char *)(page2kva(p) + 0x100) = '\0'; + assert(strlen((const char *)0x100) == 0); + + free_page(p); + free_page(pa2page(PDE_ADDR(boot_pgdir[0]))); + boot_pgdir[0] = 0; + + cprintf("check_boot_pgdir() succeeded!\n"); +} + +//perm2str - use string 'u,r,w,-' to present the permission +static const char * +perm2str(int perm) { + static char str[4]; + str[0] = (perm & PTE_U) ? 'u' : '-'; + str[1] = 'r'; + str[2] = (perm & PTE_W) ? 'w' : '-'; + str[3] = '\0'; + return str; +} + +//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space +// - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT +// - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT +// paramemters: +// left: no use ??? +// right: the high side of table's range +// start: the low side of table's range +// table: the beginning addr of table +// left_store: the pointer of the high side of table's next range +// right_store: the pointer of the low side of table's next range +// return value: 0 - not a invalid item range, perm - a valid item range with perm permission +static int +get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { + if (start >= right) { + return 0; + } + while (start < right && !(table[start] & PTE_P)) { + start ++; + } + if (start < right) { + if (left_store != NULL) { + *left_store = start; + } + int perm = (table[start ++] & PTE_USER); + while (start < right && (table[start] & PTE_USER) == perm) { + start ++; + } + if (right_store != NULL) { + *right_store = start; + } + return perm; + } + return 0; +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + cprintf("-------------------- BEGIN --------------------\n"); + size_t left, right = 0, perm; + while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, + left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + size_t l, r = left * NPTEENTRY; + while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, + l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); + } + } + cprintf("--------------------- END ---------------------\n"); +} diff --git a/code/lab8/kern/mm/pmm.h b/code/lab8/kern/mm/pmm.h new file mode 100644 index 0000000..1e229a7 --- /dev/null +++ b/code/lab8/kern/mm/pmm.h @@ -0,0 +1,145 @@ +#ifndef __KERN_MM_PMM_H__ +#define __KERN_MM_PMM_H__ + +#include +#include +#include +#include +#include + +// pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager +// only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used +// by ucore to manage the total physical memory space. +struct pmm_manager { + const char *name; // XXX_pmm_manager's name + void (*init)(void); // initialize internal description&management data structure + // (free block list, number of free block) of XXX_pmm_manager + void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to + // the initial free physical memory space + struct Page *(*alloc_pages)(size_t n); // allocate >=n pages, depend on the allocation algorithm + void (*free_pages)(struct Page *base, size_t n); // free >=n pages with "base" addr of Page descriptor structures(memlayout.h) + size_t (*nr_free_pages)(void); // return the number of free pages + void (*check)(void); // check the correctness of XXX_pmm_manager +}; + +extern const struct pmm_manager *pmm_manager; +extern pde_t *boot_pgdir; +extern uintptr_t boot_cr3; + +void pmm_init(void); + +struct Page *alloc_pages(size_t n); +void free_pages(struct Page *base, size_t n); +size_t nr_free_pages(void); + +#define alloc_page() alloc_pages(1) +#define free_page(page) free_pages(page, 1) + +pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create); +struct Page *get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store); +void page_remove(pde_t *pgdir, uintptr_t la); +int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm); + +void load_esp0(uintptr_t esp0); +void tlb_invalidate(pde_t *pgdir, uintptr_t la); +struct Page *pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm); +void unmap_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +void exit_range(pde_t *pgdir, uintptr_t start, uintptr_t end); +int copy_range(pde_t *to, pde_t *from, uintptr_t start, uintptr_t end, bool share); + +void print_pgdir(void); + +/* * + * PADDR - takes a kernel virtual address (an address that points above KERNBASE), + * where the machine's maximum 256MB of physical memory is mapped and returns the + * corresponding physical address. It panics if you pass it a non-kernel virtual address. + * */ +#define PADDR(kva) ({ \ + uintptr_t __m_kva = (uintptr_t)(kva); \ + if (__m_kva < KERNBASE) { \ + panic("PADDR called with invalid kva %08lx", __m_kva); \ + } \ + __m_kva - KERNBASE; \ + }) + +/* * + * KADDR - takes a physical address and returns the corresponding kernel virtual + * address. It panics if you pass an invalid physical address. + * */ +#define KADDR(pa) ({ \ + uintptr_t __m_pa = (pa); \ + size_t __m_ppn = PPN(__m_pa); \ + if (__m_ppn >= npage) { \ + panic("KADDR called with invalid pa %08lx", __m_pa); \ + } \ + (void *) (__m_pa + KERNBASE); \ + }) + +extern struct Page *pages; +extern size_t npage; + +static inline ppn_t +page2ppn(struct Page *page) { + return page - pages; +} + +static inline uintptr_t +page2pa(struct Page *page) { + return page2ppn(page) << PGSHIFT; +} + +static inline struct Page * +pa2page(uintptr_t pa) { + if (PPN(pa) >= npage) { + panic("pa2page called with invalid pa"); + } + return &pages[PPN(pa)]; +} + +static inline void * +page2kva(struct Page *page) { + return KADDR(page2pa(page)); +} + +static inline struct Page * +kva2page(void *kva) { + return pa2page(PADDR(kva)); +} + +static inline struct Page * +pte2page(pte_t pte) { + if (!(pte & PTE_P)) { + panic("pte2page called with invalid pte"); + } + return pa2page(PTE_ADDR(pte)); +} + +static inline struct Page * +pde2page(pde_t pde) { + return pa2page(PDE_ADDR(pde)); +} + +static inline int +page_ref(struct Page *page) { + return atomic_read(&(page->ref)); +} + +static inline void +set_page_ref(struct Page *page, int val) { + atomic_set(&(page->ref), val); +} + +static inline int +page_ref_inc(struct Page *page) { + return atomic_add_return(&(page->ref), 1); +} + +static inline int +page_ref_dec(struct Page *page) { + return atomic_sub_return(&(page->ref), 1); +} + +extern char bootstack[], bootstacktop[]; + +#endif /* !__KERN_MM_PMM_H__ */ + diff --git a/code/lab8/kern/mm/swap.c b/code/lab8/kern/mm/swap.c new file mode 100644 index 0000000..281889d --- /dev/null +++ b/code/lab8/kern/mm/swap.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// the valid vaddr for check is between 0~CHECK_VALID_VADDR-1 +#define CHECK_VALID_VIR_PAGE_NUM 5 +#define BEING_CHECK_VALID_VADDR 0X1000 +#define CHECK_VALID_VADDR (CHECK_VALID_VIR_PAGE_NUM+1)*0x1000 +// the max number of valid physical page for check +#define CHECK_VALID_PHY_PAGE_NUM 4 +// the max access seq number +#define MAX_SEQ_NO 10 + +static struct swap_manager *sm; +size_t max_swap_offset; + +volatile int swap_init_ok = 0; + +unsigned int swap_page[CHECK_VALID_VIR_PAGE_NUM]; + +unsigned int swap_in_seq_no[MAX_SEQ_NO],swap_out_seq_no[MAX_SEQ_NO]; + +static void check_swap(void); + +int +swap_init(void) +{ + swapfs_init(); + + if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) + { + panic("bad max_swap_offset %08x.\n", max_swap_offset); + } + + + sm = &swap_manager_fifo; + int r = sm->init(); + + if (r == 0) + { + swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + check_swap(); + } + + return r; +} + +int +swap_init_mm(struct mm_struct *mm) +{ + return sm->init_mm(mm); +} + +int +swap_tick_event(struct mm_struct *mm) +{ + return sm->tick_event(mm); +} + +int +swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + return sm->map_swappable(mm, addr, page, swap_in); +} + +int +swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return sm->set_unswappable(mm, addr); +} + +volatile unsigned int swap_out_num=0; + +int +swap_out(struct mm_struct *mm, int n, int in_tick) +{ + int i; + for (i = 0; i != n; ++ i) + { + uintptr_t v; + //struct Page **ptr_page=NULL; + struct Page *page; + // cprintf("i %d, SWAP: call swap_out_victim\n",i); + int r = sm->swap_out_victim(mm, &page, in_tick); + if (r != 0) { + cprintf("i %d, swap_out: call swap_out_victim failed\n",i); + break; + } + //assert(!PageReserved(page)); + + //cprintf("SWAP: choose victim page 0x%08x\n", page); + + v=page->pra_vaddr; + pte_t *ptep = get_pte(mm->pgdir, v, 0); + assert((*ptep & PTE_P) != 0); + + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { + cprintf("SWAP: failed to save\n"); + sm->map_swappable(mm, v, page, 0); + continue; + } + else { + cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; + free_page(page); + } + + tlb_invalidate(mm->pgdir, v); + } + return i; +} + +int +swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) +{ + struct Page *result = alloc_page(); + assert(result!=NULL); + + pte_t *ptep = get_pte(mm->pgdir, addr, 0); + // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); + + int r; + if ((r = swapfs_read((*ptep), result)) != 0) + { + assert(r!=0); + } + cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr); + *ptr_result=result; + return 0; +} + + + +static inline void +check_content_set(void) +{ + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x1010 = 0x0a; + assert(pgfault_num==1); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x2010 = 0x0b; + assert(pgfault_num==2); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x3010 = 0x0c; + assert(pgfault_num==3); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + *(unsigned char *)0x4010 = 0x0d; + assert(pgfault_num==4); +} + +static inline int +check_content_access(void) +{ + int ret = sm->check_swap(); + return ret; +} + +struct Page * check_rp[CHECK_VALID_PHY_PAGE_NUM]; +pte_t * check_ptep[CHECK_VALID_PHY_PAGE_NUM]; +unsigned int check_swap_addr[CHECK_VALID_VIR_PAGE_NUM]; + +extern free_area_t free_area; + +#define free_list (free_area.free_list) +#define nr_free (free_area.nr_free) + +static void +check_swap(void) +{ + //backup mem env + int ret, count = 0, total = 0, i; + list_entry_t *le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + //assert(PageProperty(p)); + count ++, total += p->property; + } + assert(total == nr_free_pages()); + cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + + //now we set the phy pages env + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + extern struct mm_struct *check_mm_struct; + assert(check_mm_struct == NULL); + + check_mm_struct = mm; + + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + //setup the temp Page Table vaddr 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + pte_t *temp_ptep=NULL; + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); + assert(temp_ptep!= NULL); + cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + + for (i=0;iphy_page environment for page relpacement algorithm + + + pgfault_num=0; + + check_content_set(); + assert( nr_free == 0); + for(i = 0; ipgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + nr_free = nr_free_store; + free_list = free_list_store; + + + le = &free_list; + while ((le = list_next(le)) != &free_list) { + struct Page *p = le2page(le, page_link); + count --, total -= p->property; + } + cprintf("count is %d, total is %d\n",count,total); + //assert(count == 0); + + cprintf("check_swap() succeeded!\n"); +} diff --git a/code/lab8/kern/mm/swap.h b/code/lab8/kern/mm/swap.h new file mode 100644 index 0000000..5d4aea8 --- /dev/null +++ b/code/lab8/kern/mm/swap.h @@ -0,0 +1,65 @@ +#ifndef __KERN_MM_SWAP_H__ +#define __KERN_MM_SWAP_H__ + +#include +#include +#include +#include + +/* * + * swap_entry_t + * -------------------------------------------- + * | offset | reserved | 0 | + * -------------------------------------------- + * 24 bits 7 bits 1 bit + * */ + +#define MAX_SWAP_OFFSET_LIMIT (1 << 24) + +extern size_t max_swap_offset; + +/* * + * swap_offset - takes a swap_entry (saved in pte), and returns + * the corresponding offset in swap mem_map. + * */ +#define swap_offset(entry) ({ \ + size_t __offset = (entry >> 8); \ + if (!(__offset > 0 && __offset < max_swap_offset)) { \ + panic("invalid swap_entry_t = %08x.\n", entry); \ + } \ + __offset; \ + }) + +struct swap_manager +{ + const char *name; + /* Global initialization for the swap manager */ + int (*init) (void); + /* Initialize the priv data inside mm_struct */ + int (*init_mm) (struct mm_struct *mm); + /* Called when tick interrupt occured */ + int (*tick_event) (struct mm_struct *mm); + /* Called when map a swappable page into the mm_struct */ + int (*map_swappable) (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); + /* When a page is marked as shared, this routine is called to + * delete the addr entry from the swap manager */ + int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr); + /* Try to swap out a page, return then victim */ + int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick); + /* check the page relpacement algorithm */ + int (*check_swap)(void); +}; + +extern volatile int swap_init_ok; +int swap_init(void); +int swap_init_mm(struct mm_struct *mm); +int swap_tick_event(struct mm_struct *mm); +int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in); +int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr); +int swap_out(struct mm_struct *mm, int n, int in_tick); +int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result); + +//#define MEMBER_OFFSET(m,t) ((int)(&((t *)0)->m)) +//#define FROM_MEMBER(m,t,a) ((t *)((char *)(a) - MEMBER_OFFSET(m,t))) + +#endif diff --git a/code/lab8/kern/mm/swap_fifo.c b/code/lab8/kern/mm/swap_fifo.c new file mode 100644 index 0000000..4cb00c1 --- /dev/null +++ b/code/lab8/kern/mm/swap_fifo.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include + +/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out + * page replacement algorithm is a low-overhead algorithm that requires little book-keeping on + * the part of the operating system. The idea is obvious from the name - the operating system + * keeps track of all the pages in memory in a queue, with the most recent arrival at the back, + * and the earliest arrival in front. When a page needs to be replaced, the page at the front + * of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs + * poorly in practical application. Thus, it is rarely used in its unmodified form. This + * algorithm experiences Belady's anomaly. + * + * Details of FIFO PRA + * (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can + * link these pages into pra_list_head according the time order. At first you should + * be familiar to the struct list in list.h. struct list is a simple doubly linked list + * implementation. You should know howto USE: list_init, list_add(list_add_after), + * list_add_before, list_del, list_next, list_prev. Another tricky method is to transform + * a general list struct to a special struct (such as struct page). You can find some MACRO: + * le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc. + */ + +list_entry_t pra_list_head; +/* + * (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head. + * Now, From the memory control struct mm_struct, we can access FIFO PRA + */ +static int +_fifo_init_mm(struct mm_struct *mm) +{ + list_init(&pra_list_head); + mm->sm_priv = &pra_list_head; + //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + return 0; +} +/* + * (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue + */ +static int +_fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + list_entry_t *entry=&(page->pra_page_link); + + assert(entry != NULL && head != NULL); + //record the page access situlation + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + return 0; +} +/* + * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, + * then set the addr of addr of this page to ptr_page. + */ +static int +_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) +{ + list_entry_t *head=(list_entry_t*) mm->sm_priv; + assert(head != NULL); + assert(in_tick==0); + /* Select the victim */ + /*LAB3 EXERCISE 2: YOUR CODE*/ + //(1) unlink the earliest arrival page in front of pra_list_head qeueue + //(2) set the addr of addr of this page to ptr_page + return 0; +} + +static int +_fifo_check_swap(void) { + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==4); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==4); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==4); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==4); + cprintf("write Virt Page e in fifo_check_swap\n"); + *(unsigned char *)0x5000 = 0x0e; + assert(pgfault_num==5); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==5); + cprintf("write Virt Page a in fifo_check_swap\n"); + *(unsigned char *)0x1000 = 0x0a; + assert(pgfault_num==6); + cprintf("write Virt Page b in fifo_check_swap\n"); + *(unsigned char *)0x2000 = 0x0b; + assert(pgfault_num==7); + cprintf("write Virt Page c in fifo_check_swap\n"); + *(unsigned char *)0x3000 = 0x0c; + assert(pgfault_num==8); + cprintf("write Virt Page d in fifo_check_swap\n"); + *(unsigned char *)0x4000 = 0x0d; + assert(pgfault_num==9); + return 0; +} + + +static int +_fifo_init(void) +{ + return 0; +} + +static int +_fifo_set_unswappable(struct mm_struct *mm, uintptr_t addr) +{ + return 0; +} + +static int +_fifo_tick_event(struct mm_struct *mm) +{ return 0; } + + +struct swap_manager swap_manager_fifo = +{ + .name = "fifo swap manager", + .init = &_fifo_init, + .init_mm = &_fifo_init_mm, + .tick_event = &_fifo_tick_event, + .map_swappable = &_fifo_map_swappable, + .set_unswappable = &_fifo_set_unswappable, + .swap_out_victim = &_fifo_swap_out_victim, + .check_swap = &_fifo_check_swap, +}; diff --git a/code/lab8/kern/mm/swap_fifo.h b/code/lab8/kern/mm/swap_fifo.h new file mode 100644 index 0000000..1d74269 --- /dev/null +++ b/code/lab8/kern/mm/swap_fifo.h @@ -0,0 +1,7 @@ +#ifndef __KERN_MM_SWAP_FIFO_H__ +#define __KERN_MM_SWAP_FIFO_H__ + +#include +extern struct swap_manager swap_manager_fifo; + +#endif diff --git a/code/lab8/kern/mm/vmm.c b/code/lab8/kern/mm/vmm.c new file mode 100644 index 0000000..26a4ab0 --- /dev/null +++ b/code/lab8/kern/mm/vmm.c @@ -0,0 +1,530 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + vmm design include two parts: mm_struct (mm) & vma_struct (vma) + mm is the memory manager for the set of continuous virtual memory + area which have the same PDT. vma is a continuous virtual memory area. + There a linear link list for vma & a redblack link list for vma in mm. +--------------- + mm related functions: + golbal functions + struct mm_struct * mm_create(void) + void mm_destroy(struct mm_struct *mm) + int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) +-------------- + vma related functions: + global functions + struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) + void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) + struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) + local functions + inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) +--------------- + check correctness functions + void check_vmm(void); + void check_vma_struct(void); + void check_pgfault(void); +*/ + +static void check_vmm(void); +static void check_vma_struct(void); +static void check_pgfault(void); + +// mm_create - alloc a mm_struct & initialize it. +struct mm_struct * +mm_create(void) { + struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); + + if (mm != NULL) { + list_init(&(mm->mmap_list)); + mm->mmap_cache = NULL; + mm->pgdir = NULL; + mm->map_count = 0; + + if (swap_init_ok) swap_init_mm(mm); + else mm->sm_priv = NULL; + + set_mm_count(mm, 0); + sem_init(&(mm->mm_sem), 1); + } + return mm; +} + +// vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +struct vma_struct * +vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); + + if (vma != NULL) { + vma->vm_start = vm_start; + vma->vm_end = vm_end; + vma->vm_flags = vm_flags; + } + return vma; +} + + +// find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +struct vma_struct * +find_vma(struct mm_struct *mm, uintptr_t addr) { + struct vma_struct *vma = NULL; + if (mm != NULL) { + vma = mm->mmap_cache; + if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { + bool found = 0; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + vma = le2vma(le, list_link); + if (addr < vma->vm_end) { + found = 1; + break; + } + } + if (!found) { + vma = NULL; + } + } + if (vma != NULL) { + mm->mmap_cache = vma; + } + } + return vma; +} + + +// check_vma_overlap - check if vma1 overlaps vma2 ? +static inline void +check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { + assert(prev->vm_start < prev->vm_end); + assert(prev->vm_end <= next->vm_start); + assert(next->vm_start < next->vm_end); +} + + +// insert_vma_struct -insert vma in mm's list link +void +insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + assert(vma->vm_start < vma->vm_end); + list_entry_t *list = &(mm->mmap_list); + list_entry_t *le_prev = list, *le_next; + + list_entry_t *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *mmap_prev = le2vma(le, list_link); + if (mmap_prev->vm_start > vma->vm_start) { + break; + } + le_prev = le; + } + + le_next = list_next(le_prev); + + /* check overlap */ + if (le_prev != list) { + check_vma_overlap(le2vma(le_prev, list_link), vma); + } + if (le_next != list) { + check_vma_overlap(vma, le2vma(le_next, list_link)); + } + + vma->vm_mm = mm; + list_add_after(le_prev, &(vma->list_link)); + + mm->map_count ++; +} + +// mm_destroy - free mm and mm internal fields +void +mm_destroy(struct mm_struct *mm) { + assert(mm_count(mm) == 0); + + list_entry_t *list = &(mm->mmap_list), *le; + while ((le = list_next(list)) != list) { + list_del(le); + kfree(le2vma(le, list_link)); //kfree vma + } + kfree(mm); //kfree mm + mm=NULL; +} + +int +mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store) { + uintptr_t start = ROUNDDOWN(addr, PGSIZE), end = ROUNDUP(addr + len, PGSIZE); + if (!USER_ACCESS(start, end)) { + return -E_INVAL; + } + + assert(mm != NULL); + + int ret = -E_INVAL; + + struct vma_struct *vma; + if ((vma = find_vma(mm, start)) != NULL && end > vma->vm_start) { + goto out; + } + ret = -E_NO_MEM; + + if ((vma = vma_create(start, end, vm_flags)) == NULL) { + goto out; + } + insert_vma_struct(mm, vma); + if (vma_store != NULL) { + *vma_store = vma; + } + ret = 0; + +out: + return ret; +} + +int +dup_mmap(struct mm_struct *to, struct mm_struct *from) { + assert(to != NULL && from != NULL); + list_entry_t *list = &(from->mmap_list), *le = list; + while ((le = list_prev(le)) != list) { + struct vma_struct *vma, *nvma; + vma = le2vma(le, list_link); + nvma = vma_create(vma->vm_start, vma->vm_end, vma->vm_flags); + if (nvma == NULL) { + return -E_NO_MEM; + } + + insert_vma_struct(to, nvma); + + bool share = 0; + if (copy_range(to->pgdir, from->pgdir, vma->vm_start, vma->vm_end, share) != 0) { + return -E_NO_MEM; + } + } + return 0; +} + +void +exit_mmap(struct mm_struct *mm) { + assert(mm != NULL && mm_count(mm) == 0); + pde_t *pgdir = mm->pgdir; + list_entry_t *list = &(mm->mmap_list), *le = list; + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + unmap_range(pgdir, vma->vm_start, vma->vm_end); + } + while ((le = list_next(le)) != list) { + struct vma_struct *vma = le2vma(le, list_link); + exit_range(pgdir, vma->vm_start, vma->vm_end); + } +} + +bool +copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable) { + if (!user_mem_check(mm, (uintptr_t)src, len, writable)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +bool +copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len) { + if (!user_mem_check(mm, (uintptr_t)dst, len, 1)) { + return 0; + } + memcpy(dst, src, len); + return 1; +} + +// vmm_init - initialize virtual memory management +// - now just call check_vmm to check correctness of vmm +void +vmm_init(void) { + check_vmm(); +} + +// check_vmm - check correctness of vmm +static void +check_vmm(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_vma_struct(); + check_pgfault(); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vmm() succeeded.\n"); +} + +static void +check_vma_struct(void) { + size_t nr_free_pages_store = nr_free_pages(); + + struct mm_struct *mm = mm_create(); + assert(mm != NULL); + + int step1 = 10, step2 = step1 * 10; + + int i; + for (i = step1; i >= 0; i --) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + for (i = step1 + 1; i <= step2; i ++) { + struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); + assert(vma != NULL); + insert_vma_struct(mm, vma); + } + + list_entry_t *le = list_next(&(mm->mmap_list)); + + for (i = 0; i <= step2; i ++) { + assert(le != &(mm->mmap_list)); + struct vma_struct *mmap = le2vma(le, list_link); + assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); + le = list_next(le); + } + + for (i = 0; i < 5 * step2 + 2; i ++) { + struct vma_struct *vma = find_vma(mm, i); + assert(vma != NULL); + int j = i / 5; + if (i >= 5 * j + 2) { + j ++; + } + assert(vma->vm_start == j * 5 && vma->vm_end == j * 5 + 2); + } + + mm_destroy(mm); + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_vma_struct() succeeded!\n"); +} + +struct mm_struct *check_mm_struct; + +// check_pgfault - check correctness of pgfault handler +static void +check_pgfault(void) { + size_t nr_free_pages_store = nr_free_pages(); + + check_mm_struct = mm_create(); + assert(check_mm_struct != NULL); + + struct mm_struct *mm = check_mm_struct; + pde_t *pgdir = mm->pgdir = boot_pgdir; + assert(pgdir[0] == 0); + + struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + assert(vma != NULL); + + insert_vma_struct(mm, vma); + + uintptr_t addr = 0x100; + assert(find_vma(mm, addr) == vma); + + int i, sum = 0; + for (i = 0; i < 100; i ++) { + *(char *)(addr + i) = i; + sum += i; + } + for (i = 0; i < 100; i ++) { + sum -= *(char *)(addr + i); + } + assert(sum == 0); + + page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + free_page(pa2page(pgdir[0])); + pgdir[0] = 0; + + mm->pgdir = NULL; + mm_destroy(mm); + check_mm_struct = NULL; + + assert(nr_free_pages_store == nr_free_pages()); + + cprintf("check_pgfault() succeeded!\n"); +} +//page fault number +volatile unsigned int pgfault_num=0; + +/* do_pgfault - interrupt handler to process the page fault execption + * @mm : the control struct for a set of vma using the same PDT + * @error_code : the error code recorded in trapframe->tf_err which is setted by x86 hardware + * @addr : the addr which causes a memory access exception, (the contents of the CR2 register) + * + * CALL GRAPH: trap--> trap_dispatch-->pgfault_handler-->do_pgfault + * The processor provides ucore's do_pgfault function with two items of information to aid in diagnosing + * the exception and recovering from it. + * (1) The contents of the CR2 register. The processor loads the CR2 register with the + * 32-bit linear address that generated the exception. The do_pgfault fun can + * use this address to locate the corresponding page directory and page-table + * entries. + * (2) An error code on the kernel stack. The error code for a page fault has a format different from + * that for other exceptions. The error code tells the exception handler three things: + * -- The P flag (bit 0) indicates whether the exception was due to a not-present page (0) + * or to either an access rights violation or the use of a reserved bit (1). + * -- The W/R flag (bit 1) indicates whether the memory access that caused the exception + * was a read (0) or write (1). + * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) + * or supervisor mode (0) at the time of the exception. + */ +int +do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { + int ret = -E_INVAL; + //try to find a vma which include addr + struct vma_struct *vma = find_vma(mm, addr); + + pgfault_num++; + //If the addr is in the range of a mm's vma? + if (vma == NULL || vma->vm_start > addr) { + cprintf("not valid addr %x, and can not find it in vma\n", addr); + goto failed; + } + //check the error_code + switch (error_code & 3) { + default: + /* error code flag : default is 3 ( W/R=1, P=1): write, present */ + case 2: /* error code flag : (W/R=1, P=0): write, not present */ + if (!(vma->vm_flags & VM_WRITE)) { + cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); + goto failed; + } + break; + case 1: /* error code flag : (W/R=0, P=1): read, present */ + cprintf("do_pgfault failed: error code flag = read AND present\n"); + goto failed; + case 0: /* error code flag : (W/R=0, P=0): read, not present */ + if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { + cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); + goto failed; + } + } + /* IF (write an existed addr ) OR + * (write an non_existed addr && addr is writable) OR + * (read an non_existed addr && addr is readable) + * THEN + * continue process + */ + uint32_t perm = PTE_U; + if (vma->vm_flags & VM_WRITE) { + perm |= PTE_W; + } + addr = ROUNDDOWN(addr, PGSIZE); + + ret = -E_NO_MEM; + + pte_t *ptep=NULL; + /*LAB3 EXERCISE 1: YOUR CODE + * Maybe you want help comment, BELOW comments can help you finish the code + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * get_pte : get an pte and return the kernel virtual address of this pte for la + * if the PT contians this pte didn't exist, alloc a page for PT (notice the 3th parameter '1') + * pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup + * an addr map pa<--->la with linear address la and the PDT pgdir + * DEFINES: + * VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable + * PTE_W 0x002 // page table/directory entry flags bit : Writeable + * PTE_U 0x004 // page table/directory entry flags bit : User can access + * VARIABLES: + * mm->pgdir : the PDT of these vma + * + */ +#if 0 + /*LAB3 EXERCISE 1: YOUR CODE*/ + ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + if (*ptep == 0) { + //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + + } + else { + /*LAB3 EXERCISE 2: YOUR CODE + * Now we think this pte is a swap entry, we should load data from disk to a page with phy addr, + * and map the phy addr with logical addr, trigger swap manager to record the access situation of this page. + * + * Some Useful MACROs and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr, + * find the addr of disk page, read the content of disk page into this memroy page + * page_insert : build the map of phy addr of an Page with the linear addr la + * swap_map_swappable : set the page swappable + */ + if(swap_init_ok) { + struct Page *page=NULL; + //(1)According to the mm AND addr, try to load the content of right disk page + // into the memory which page managed. + //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr + //(3) make the page swappable. + //(4) [NOTICE]: you myabe need to update your lab3's implementation for LAB5's normal execution. + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed; + } + } +#endif + ret = 0; +failed: + return ret; +} + +bool +user_mem_check(struct mm_struct *mm, uintptr_t addr, size_t len, bool write) { + if (mm != NULL) { + if (!USER_ACCESS(addr, addr + len)) { + return 0; + } + struct vma_struct *vma; + uintptr_t start = addr, end = addr + len; + while (start < end) { + if ((vma = find_vma(mm, start)) == NULL || start < vma->vm_start) { + return 0; + } + if (!(vma->vm_flags & ((write) ? VM_WRITE : VM_READ))) { + return 0; + } + if (write && (vma->vm_flags & VM_STACK)) { + if (start < vma->vm_start + PGSIZE) { //check stack start & size + return 0; + } + } + start = vma->vm_end; + } + return 1; + } + return KERN_ACCESS(addr, addr + len); +} + +bool +copy_string(struct mm_struct *mm, char *dst, const char *src, size_t maxn) { + size_t alen, part = ROUNDDOWN((uintptr_t)src + PGSIZE, PGSIZE) - (uintptr_t)src; + while (1) { + if (part > maxn) { + part = maxn; + } + if (!user_mem_check(mm, (uintptr_t)src, part, 0)) { + return 0; + } + if ((alen = strnlen(src, part)) < part) { + memcpy(dst, src, alen + 1); + return 1; + } + if (part == maxn) { + return 0; + } + memcpy(dst, src, part); + dst += part, src += part, maxn -= part; + part = PGSIZE; + } +} diff --git a/code/lab8/kern/mm/vmm.h b/code/lab8/kern/mm/vmm.h new file mode 100644 index 0000000..3296df6 --- /dev/null +++ b/code/lab8/kern/mm/vmm.h @@ -0,0 +1,109 @@ +#ifndef __KERN_MM_VMM_H__ +#define __KERN_MM_VMM_H__ + +#include +#include +#include +#include +#include +#include + +//pre define +struct mm_struct; + +// the virtual continuous memory area(vma) +struct vma_struct { + struct mm_struct *vm_mm; // the set of vma using the same PDT + uintptr_t vm_start; // start addr of vma + uintptr_t vm_end; // end addr of vma + uint32_t vm_flags; // flags of vma + list_entry_t list_link; // linear list link which sorted by start addr of vma +}; + +#define le2vma(le, member) \ + to_struct((le), struct vma_struct, member) + +#define VM_READ 0x00000001 +#define VM_WRITE 0x00000002 +#define VM_EXEC 0x00000004 +#define VM_STACK 0x00000008 + +// the control struct for a set of vma using the same PDT +struct mm_struct { + list_entry_t mmap_list; // linear list link which sorted by start addr of vma + struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose + pde_t *pgdir; // the PDT of these vma + int map_count; // the count of these vma + void *sm_priv; // the private data for swap manager + atomic_t mm_count; // the number ofprocess which shared the mm + semaphore_t mm_sem; // mutex for using dup_mmap fun to duplicat the mm + int locked_by; // the lock owner process's pid + +}; + +struct vma_struct *find_vma(struct mm_struct *mm, uintptr_t addr); +struct vma_struct *vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags); +void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma); + +struct mm_struct *mm_create(void); +void mm_destroy(struct mm_struct *mm); + +void vmm_init(void); +int mm_map(struct mm_struct *mm, uintptr_t addr, size_t len, uint32_t vm_flags, + struct vma_struct **vma_store); +int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr); + +int mm_unmap(struct mm_struct *mm, uintptr_t addr, size_t len); +int dup_mmap(struct mm_struct *to, struct mm_struct *from); +void exit_mmap(struct mm_struct *mm); +uintptr_t get_unmapped_area(struct mm_struct *mm, size_t len); +int mm_brk(struct mm_struct *mm, uintptr_t addr, size_t len); + +extern volatile unsigned int pgfault_num; +extern struct mm_struct *check_mm_struct; + +bool user_mem_check(struct mm_struct *mm, uintptr_t start, size_t len, bool write); +bool copy_from_user(struct mm_struct *mm, void *dst, const void *src, size_t len, bool writable); +bool copy_to_user(struct mm_struct *mm, void *dst, const void *src, size_t len); +bool copy_string(struct mm_struct *mm, char *dst, const char *src, size_t maxn); + +static inline int +mm_count(struct mm_struct *mm) { + return atomic_read(&(mm->mm_count)); +} + +static inline void +set_mm_count(struct mm_struct *mm, int val) { + atomic_set(&(mm->mm_count), val); +} + +static inline int +mm_count_inc(struct mm_struct *mm) { + return atomic_add_return(&(mm->mm_count), 1); +} + +static inline int +mm_count_dec(struct mm_struct *mm) { + return atomic_sub_return(&(mm->mm_count), 1); +} + +static inline void +lock_mm(struct mm_struct *mm) { + if (mm != NULL) { + down(&(mm->mm_sem)); + if (current != NULL) { + mm->locked_by = current->pid; + } + } +} + +static inline void +unlock_mm(struct mm_struct *mm) { + if (mm != NULL) { + up(&(mm->mm_sem)); + mm->locked_by = 0; + } +} + +#endif /* !__KERN_MM_VMM_H__ */ + diff --git a/code/lab8/kern/process/entry.S b/code/lab8/kern/process/entry.S new file mode 100644 index 0000000..7482e23 --- /dev/null +++ b/code/lab8/kern/process/entry.S @@ -0,0 +1,10 @@ +.text +.globl kernel_thread_entry +kernel_thread_entry: # void kernel_thread(void) + + pushl %edx # push arg + call *%ebx # call fn + + pushl %eax # save the return value of fn(arg) + call do_exit # call do_exit to terminate current thread + diff --git a/code/lab8/kern/process/proc.c b/code/lab8/kern/process/proc.c new file mode 100644 index 0000000..a4c095e --- /dev/null +++ b/code/lab8/kern/process/proc.c @@ -0,0 +1,896 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------- process/thread mechanism design&implementation ------------- +(an simplified Linux process/thread mechanism ) +introduction: + ucore implements a simple process/thread mechanism. process contains the independent memory sapce, at least one threads +for execution, the kernel data(for management), processor state (for context switch), files(in lab6), etc. ucore needs to +manage all these details efficiently. In ucore, a thread is just a special kind of process(share process's memory). +------------------------------ +process state : meaning -- reason + PROC_UNINIT : uninitialized -- alloc_proc + PROC_SLEEPING : sleeping -- try_free_pages, do_wait, do_sleep + PROC_RUNNABLE : runnable(maybe running) -- proc_init, wakeup_proc, + PROC_ZOMBIE : almost dead -- do_exit + +----------------------------- +process state changing: + + alloc_proc RUNNING + + +--<----<--+ + + + proc_run + + V +-->---->--+ +PROC_UNINIT -- proc_init/wakeup_proc --> PROC_RUNNABLE -- try_free_pages/do_wait/do_sleep --> PROC_SLEEPING -- + A + + + | +--- do_exit --> PROC_ZOMBIE + + + + + -----------------------wakeup_proc---------------------------------- +----------------------------- +process relations +parent: proc->parent (proc is children) +children: proc->cptr (proc is parent) +older sibling: proc->optr (proc is younger sibling) +younger sibling: proc->yptr (proc is older sibling) +----------------------------- +related syscall for process: +SYS_exit : process exit, -->do_exit +SYS_fork : create child process, dup mm -->do_fork-->wakeup_proc +SYS_wait : wait process -->do_wait +SYS_exec : after fork, process execute a program -->load a program and refresh the mm +SYS_clone : create child thread -->do_fork-->wakeup_proc +SYS_yield : process flag itself need resecheduling, -- proc->need_sched=1, then scheduler will rescheule this process +SYS_sleep : process sleep -->do_sleep +SYS_kill : kill process -->do_kill-->proc->flags |= PF_EXITING + -->wakeup_proc-->do_wait-->do_exit +SYS_getpid : get the process's pid + +*/ + +// the process set's list +list_entry_t proc_list; + +#define HASH_SHIFT 10 +#define HASH_LIST_SIZE (1 << HASH_SHIFT) +#define pid_hashfn(x) (hash32(x, HASH_SHIFT)) + +// has list for process set based on pid +static list_entry_t hash_list[HASH_LIST_SIZE]; + +// idle proc +struct proc_struct *idleproc = NULL; +// init proc +struct proc_struct *initproc = NULL; +// current proc +struct proc_struct *current = NULL; + +static int nr_process = 0; + +void kernel_thread_entry(void); +void forkrets(struct trapframe *tf); +void switch_to(struct context *from, struct context *to); + +// alloc_proc - alloc a proc_struct and init all fields of proc_struct +static struct proc_struct * +alloc_proc(void) { + struct proc_struct *proc = kmalloc(sizeof(struct proc_struct)); + if (proc != NULL) { + //LAB4:EXERCISE1 YOUR CODE + /* + * below fields in proc_struct need to be initialized + * enum proc_state state; // Process state + * int pid; // Process ID + * int runs; // the running times of Proces + * uintptr_t kstack; // Process kernel stack + * volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + * struct proc_struct *parent; // the parent process + * struct mm_struct *mm; // Process's memory management field + * struct context context; // Switch here to run process + * struct trapframe *tf; // Trap frame for current interrupt + * uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + * uint32_t flags; // Process flag + * char name[PROC_NAME_LEN + 1]; // Process name + */ + //LAB8:EXERCISE2 YOUR CODE HINT:need add some code to init fs in proc_struct, ... + } + return proc; +} + +// set_proc_name - set the name of proc +char * +set_proc_name(struct proc_struct *proc, const char *name) { + memset(proc->name, 0, sizeof(proc->name)); + return memcpy(proc->name, name, PROC_NAME_LEN); +} + +// get_proc_name - get the name of proc +char * +get_proc_name(struct proc_struct *proc) { + static char name[PROC_NAME_LEN + 1]; + memset(name, 0, sizeof(name)); + return memcpy(name, proc->name, PROC_NAME_LEN); +} + +// set_links - set the relation links of process +static void +set_links(struct proc_struct *proc) { + list_add(&proc_list, &(proc->list_link)); + proc->yptr = NULL; + if ((proc->optr = proc->parent->cptr) != NULL) { + proc->optr->yptr = proc; + } + proc->parent->cptr = proc; + nr_process ++; +} + +// remove_links - clean the relation links of process +static void +remove_links(struct proc_struct *proc) { + list_del(&(proc->list_link)); + if (proc->optr != NULL) { + proc->optr->yptr = proc->yptr; + } + if (proc->yptr != NULL) { + proc->yptr->optr = proc->optr; + } + else { + proc->parent->cptr = proc->optr; + } + nr_process --; +} + +// get_pid - alloc a unique pid for process +static int +get_pid(void) { + static_assert(MAX_PID > MAX_PROCESS); + struct proc_struct *proc; + list_entry_t *list = &proc_list, *le; + static int next_safe = MAX_PID, last_pid = MAX_PID; + if (++ last_pid >= MAX_PID) { + last_pid = 1; + goto inside; + } + if (last_pid >= next_safe) { + inside: + next_safe = MAX_PID; + repeat: + le = list; + while ((le = list_next(le)) != list) { + proc = le2proc(le, list_link); + if (proc->pid == last_pid) { + if (++ last_pid >= next_safe) { + if (last_pid >= MAX_PID) { + last_pid = 1; + } + next_safe = MAX_PID; + goto repeat; + } + } + else if (proc->pid > last_pid && next_safe > proc->pid) { + next_safe = proc->pid; + } + } + } + return last_pid; +} + +// proc_run - make process "proc" running on cpu +// NOTE: before call switch_to, should load base addr of "proc"'s new PDT +void +proc_run(struct proc_struct *proc) { + if (proc != current) { + bool intr_flag; + struct proc_struct *prev = current, *next = proc; + local_intr_save(intr_flag); + { + current = proc; + load_esp0(next->kstack + KSTACKSIZE); + lcr3(next->cr3); + switch_to(&(prev->context), &(next->context)); + } + local_intr_restore(intr_flag); + } +} + +// forkret -- the first kernel entry point of a new thread/process +// NOTE: the addr of forkret is setted in copy_thread function +// after switch_to, the current proc will execute here. +static void +forkret(void) { + forkrets(current->tf); +} + +// hash_proc - add proc into proc hash_list +static void +hash_proc(struct proc_struct *proc) { + list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); +} + +// unhash_proc - delete proc from proc hash_list +static void +unhash_proc(struct proc_struct *proc) { + list_del(&(proc->hash_link)); +} + +// find_proc - find proc frome proc hash_list according to pid +struct proc_struct * +find_proc(int pid) { + if (0 < pid && pid < MAX_PID) { + list_entry_t *list = hash_list + pid_hashfn(pid), *le = list; + while ((le = list_next(le)) != list) { + struct proc_struct *proc = le2proc(le, hash_link); + if (proc->pid == pid) { + return proc; + } + } + } + return NULL; +} + +// kernel_thread - create a kernel thread using "fn" function +// NOTE: the contents of temp trapframe tf will be copied to +// proc->tf in do_fork-->copy_thread function +int +kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { + struct trapframe tf; + memset(&tf, 0, sizeof(struct trapframe)); + tf.tf_cs = KERNEL_CS; + tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS; + tf.tf_regs.reg_ebx = (uint32_t)fn; + tf.tf_regs.reg_edx = (uint32_t)arg; + tf.tf_eip = (uint32_t)kernel_thread_entry; + return do_fork(clone_flags | CLONE_VM, 0, &tf); +} + +// setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack +static int +setup_kstack(struct proc_struct *proc) { + struct Page *page = alloc_pages(KSTACKPAGE); + if (page != NULL) { + proc->kstack = (uintptr_t)page2kva(page); + return 0; + } + return -E_NO_MEM; +} + +// put_kstack - free the memory space of process kernel stack +static void +put_kstack(struct proc_struct *proc) { + free_pages(kva2page((void *)(proc->kstack)), KSTACKPAGE); +} + +// setup_pgdir - alloc one page as PDT +static int +setup_pgdir(struct mm_struct *mm) { + struct Page *page; + if ((page = alloc_page()) == NULL) { + return -E_NO_MEM; + } + pde_t *pgdir = page2kva(page); + memcpy(pgdir, boot_pgdir, PGSIZE); + pgdir[PDX(VPT)] = PADDR(pgdir) | PTE_P | PTE_W; + mm->pgdir = pgdir; + return 0; +} + +// put_pgdir - free the memory space of PDT +static void +put_pgdir(struct mm_struct *mm) { + free_page(kva2page(mm->pgdir)); +} + +// copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags +// - if clone_flags & CLONE_VM, then "share" ; else "duplicate" +static int +copy_mm(uint32_t clone_flags, struct proc_struct *proc) { + struct mm_struct *mm, *oldmm = current->mm; + + /* current is a kernel thread */ + if (oldmm == NULL) { + return 0; + } + if (clone_flags & CLONE_VM) { + mm = oldmm; + goto good_mm; + } + + int ret = -E_NO_MEM; + if ((mm = mm_create()) == NULL) { + goto bad_mm; + } + if (setup_pgdir(mm) != 0) { + goto bad_pgdir_cleanup_mm; + } + + lock_mm(oldmm); + { + ret = dup_mmap(mm, oldmm); + } + unlock_mm(oldmm); + + if (ret != 0) { + goto bad_dup_cleanup_mmap; + } + +good_mm: + mm_count_inc(mm); + proc->mm = mm; + proc->cr3 = PADDR(mm->pgdir); + return 0; +bad_dup_cleanup_mmap: + exit_mmap(mm); + put_pgdir(mm); +bad_pgdir_cleanup_mm: + mm_destroy(mm); +bad_mm: + return ret; +} + +// copy_thread - setup the trapframe on the process's kernel stack top and +// - setup the kernel entry point and stack of process +static void +copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { + proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; + *(proc->tf) = *tf; + proc->tf->tf_regs.reg_eax = 0; + proc->tf->tf_esp = esp; + proc->tf->tf_eflags |= FL_IF; + + proc->context.eip = (uintptr_t)forkret; + proc->context.esp = (uintptr_t)(proc->tf); +} + +//copy_fs&put_fs function used by do_fork in LAB8 +static int +copy_fs(uint32_t clone_flags, struct proc_struct *proc) { + struct files_struct *filesp, *old_filesp = current->filesp; + assert(old_filesp != NULL); + + if (clone_flags & CLONE_FS) { + filesp = old_filesp; + goto good_files_struct; + } + + int ret = -E_NO_MEM; + if ((filesp = files_create()) == NULL) { + goto bad_files_struct; + } + + if ((ret = dup_fs(filesp, old_filesp)) != 0) { + goto bad_dup_cleanup_fs; + } + +good_files_struct: + files_count_inc(filesp); + proc->filesp = filesp; + return 0; + +bad_dup_cleanup_fs: + files_destroy(filesp); +bad_files_struct: + return ret; +} + +static void +put_fs(struct proc_struct *proc) { + struct files_struct *filesp = proc->filesp; + if (filesp != NULL) { + if (files_count_dec(filesp) == 0) { + files_destroy(filesp); + } + } +} + +/* do_fork - parent process for a new child process + * @clone_flags: used to guide how to clone the child process + * @stack: the parent's user stack pointer. if stack==0, It means to fork a kernel thread. + * @tf: the trapframe info, which will be copied to child process's proc->tf + */ +int +do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { + int ret = -E_NO_FREE_PROC; + struct proc_struct *proc; + if (nr_process >= MAX_PROCESS) { + goto fork_out; + } + ret = -E_NO_MEM; + //LAB4:EXERCISE2 YOUR CODE + //LAB8:EXERCISE2 YOUR CODE HINT:how to copy the fs in parent's proc_struct? + /* + * Some Useful MACROs, Functions and DEFINEs, you can use them in below implementation. + * MACROs or Functions: + * alloc_proc: create a proc struct and init fields (lab4:exercise1) + * setup_kstack: alloc pages with size KSTACKPAGE as process kernel stack + * copy_mm: process "proc" duplicate OR share process "current"'s mm according clone_flags + * if clone_flags & CLONE_VM, then "share" ; else "duplicate" + * copy_thread: setup the trapframe on the process's kernel stack top and + * setup the kernel entry point and stack of process + * hash_proc: add proc into proc hash_list + * get_pid: alloc a unique pid for process + * wakup_proc: set proc->state = PROC_RUNNABLE + * VARIABLES: + * proc_list: the process set's list + * nr_process: the number of process set + */ + + // 1. call alloc_proc to allocate a proc_struct + // 2. call setup_kstack to allocate a kernel stack for child process + // 3. call copy_mm to dup OR share mm according clone_flag + // 4. call copy_thread to setup tf & context in proc_struct + // 5. insert proc_struct into hash_list && proc_list + // 6. call wakup_proc to make the new child process RUNNABLE + // 7. set ret vaule using child proc's pid +fork_out: + return ret; + +bad_fork_cleanup_fs: //for LAB8 + put_fs(proc); +bad_fork_cleanup_kstack: + put_kstack(proc); +bad_fork_cleanup_proc: + kfree(proc); + goto fork_out; +} + +// do_exit - called by sys_exit +// 1. call exit_mmap & put_pgdir & mm_destroy to free the almost all memory space of process +// 2. set process' state as PROC_ZOMBIE, then call wakeup_proc(parent) to ask parent reclaim itself. +// 3. call scheduler to switch to other process +int +do_exit(int error_code) { + if (current == idleproc) { + panic("idleproc exit.\n"); + } + if (current == initproc) { + panic("initproc exit.\n"); + } + + struct mm_struct *mm = current->mm; + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + put_fs(current); //for LAB8 + current->state = PROC_ZOMBIE; + current->exit_code = error_code; + + bool intr_flag; + struct proc_struct *proc; + local_intr_save(intr_flag); + { + proc = current->parent; + if (proc->wait_state == WT_CHILD) { + wakeup_proc(proc); + } + while (current->cptr != NULL) { + proc = current->cptr; + current->cptr = proc->optr; + + proc->yptr = NULL; + if ((proc->optr = initproc->cptr) != NULL) { + initproc->cptr->yptr = proc; + } + proc->parent = initproc; + initproc->cptr = proc; + if (proc->state == PROC_ZOMBIE) { + if (initproc->wait_state == WT_CHILD) { + wakeup_proc(initproc); + } + } + } + } + local_intr_restore(intr_flag); + + schedule(); + panic("do_exit will not return!! %d.\n", current->pid); +} + +//load_icode_read is used by load_icode in LAB8 +static int +load_icode_read(int fd, void *buf, size_t len, off_t offset) { + int ret; + if ((ret = sysfile_seek(fd, offset, LSEEK_SET)) != 0) { + return ret; + } + if ((ret = sysfile_read(fd, buf, len)) != len) { + return (ret < 0) ? ret : -1; + } + return 0; +} + +// load_icode - called by sys_exec-->do_execve + +static int +load_icode(int fd, int argc, char **kargv) { + /* LAB8:EXERCISE2 YOUR CODE HINT:how to load the file with handler fd in to process's memory? how to setup argc/argv? + * MACROs or Functions: + * mm_create - create a mm + * setup_pgdir - setup pgdir in mm + * load_icode_read - read raw data content of program file + * mm_map - build new vma + * pgdir_alloc_page - allocate new memory for TEXT/DATA/BSS/stack parts + * lcr3 - update Page Directory Addr Register -- CR3 + */ + /* (1) create a new mm for current process + * (2) create a new PDT, and mm->pgdir= kernel virtual addr of PDT + * (3) copy TEXT/DATA/BSS parts in binary to memory space of process + * (3.1) read raw data content in file and resolve elfhdr + * (3.2) read raw data content in file and resolve proghdr based on info in elfhdr + * (3.3) call mm_map to build vma related to TEXT/DATA + * (3.4) callpgdir_alloc_page to allocate page for TEXT/DATA, read contents in file + * and copy them into the new allocated pages + * (3.5) callpgdir_alloc_page to allocate pages for BSS, memset zero in these pages + * (4) call mm_map to setup user stack, and put parameters into user stack + * (5) setup current process's mm, cr3, reset pgidr (using lcr3 MARCO) + * (6) setup uargc and uargv in user stacks + * (7) setup trapframe for user environment + * (8) if up steps failed, you should cleanup the env. + */ +} + +// this function isn't very correct in LAB8 +static void +put_kargv(int argc, char **kargv) { + while (argc > 0) { + kfree(kargv[-- argc]); + } +} + +static int +copy_kargv(struct mm_struct *mm, int argc, char **kargv, const char **argv) { + int i, ret = -E_INVAL; + if (!user_mem_check(mm, (uintptr_t)argv, sizeof(const char *) * argc, 0)) { + return ret; + } + for (i = 0; i < argc; i ++) { + char *buffer; + if ((buffer = kmalloc(EXEC_MAX_ARG_LEN + 1)) == NULL) { + goto failed_nomem; + } + if (!copy_string(mm, buffer, argv[i], EXEC_MAX_ARG_LEN + 1)) { + kfree(buffer); + goto failed_cleanup; + } + kargv[i] = buffer; + } + return 0; + +failed_nomem: + ret = -E_NO_MEM; +failed_cleanup: + put_kargv(i, kargv); + return ret; +} + +// do_execve - call exit_mmap(mm)&pug_pgdir(mm) to reclaim memory space of current process +// - call load_icode to setup new memory space accroding binary prog. +int +do_execve(const char *name, int argc, const char **argv) { + static_assert(EXEC_MAX_ARG_LEN >= FS_MAX_FPATH_LEN); + struct mm_struct *mm = current->mm; + if (!(argc >= 1 && argc <= EXEC_MAX_ARG_NUM)) { + return -E_INVAL; + } + + char local_name[PROC_NAME_LEN + 1]; + memset(local_name, 0, sizeof(local_name)); + + char *kargv[EXEC_MAX_ARG_NUM]; + const char *path; + + int ret = -E_INVAL; + + lock_mm(mm); + if (name == NULL) { + snprintf(local_name, sizeof(local_name), " %d", current->pid); + } + else { + if (!copy_string(mm, local_name, name, sizeof(local_name))) { + unlock_mm(mm); + return ret; + } + } + if ((ret = copy_kargv(mm, argc, kargv, argv)) != 0) { + unlock_mm(mm); + return ret; + } + path = argv[0]; + unlock_mm(mm); + files_closeall(current->filesp); + + /* sysfile_open will check the first argument path, thus we have to use a user-space pointer, and argv[0] may be incorrect */ + int fd; + if ((ret = fd = sysfile_open(path, O_RDONLY)) < 0) { + goto execve_exit; + } + if (mm != NULL) { + lcr3(boot_cr3); + if (mm_count_dec(mm) == 0) { + exit_mmap(mm); + put_pgdir(mm); + mm_destroy(mm); + } + current->mm = NULL; + } + ret= -E_NO_MEM;; + if ((ret = load_icode(fd, argc, kargv)) != 0) { + goto execve_exit; + } + put_kargv(argc, kargv); + set_proc_name(current, local_name); + return 0; + +execve_exit: + put_kargv(argc, kargv); + do_exit(ret); + panic("already exit: %e.\n", ret); +} + +// do_yield - ask the scheduler to reschedule +int +do_yield(void) { + current->need_resched = 1; + return 0; +} + +// do_wait - wait one OR any children with PROC_ZOMBIE state, and free memory space of kernel stack +// - proc struct of this child. +// NOTE: only after do_wait function, all resources of the child proces are free. +int +do_wait(int pid, int *code_store) { + struct mm_struct *mm = current->mm; + if (code_store != NULL) { + if (!user_mem_check(mm, (uintptr_t)code_store, sizeof(int), 1)) { + return -E_INVAL; + } + } + + struct proc_struct *proc; + bool intr_flag, haskid; +repeat: + haskid = 0; + if (pid != 0) { + proc = find_proc(pid); + if (proc != NULL && proc->parent == current) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + else { + proc = current->cptr; + for (; proc != NULL; proc = proc->optr) { + haskid = 1; + if (proc->state == PROC_ZOMBIE) { + goto found; + } + } + } + if (haskid) { + current->state = PROC_SLEEPING; + current->wait_state = WT_CHILD; + schedule(); + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + goto repeat; + } + return -E_BAD_PROC; + +found: + if (proc == idleproc || proc == initproc) { + panic("wait idleproc or initproc.\n"); + } + if (code_store != NULL) { + *code_store = proc->exit_code; + } + local_intr_save(intr_flag); + { + unhash_proc(proc); + remove_links(proc); + } + local_intr_restore(intr_flag); + put_kstack(proc); + kfree(proc); + return 0; +} + +// do_kill - kill process with pid by set this process's flags with PF_EXITING +int +do_kill(int pid) { + struct proc_struct *proc; + if ((proc = find_proc(pid)) != NULL) { + if (!(proc->flags & PF_EXITING)) { + proc->flags |= PF_EXITING; + if (proc->wait_state & WT_INTERRUPTED) { + wakeup_proc(proc); + } + return 0; + } + return -E_KILLED; + } + return -E_INVAL; +} + +// kernel_execve - do SYS_exec syscall to exec a user program called by user_main kernel_thread +static int +kernel_execve(const char *name, const char **argv) { + int argc = 0, ret; + while (argv[argc] != NULL) { + argc ++; + } + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), "0" (SYS_exec), "d" (name), "c" (argc), "b" (argv) + : "memory"); + return ret; +} + +#define __KERNEL_EXECVE(name, path, ...) ({ \ +const char *argv[] = {path, ##__VA_ARGS__, NULL}; \ + cprintf("kernel_execve: pid = %d, name = \"%s\".\n", \ + current->pid, name); \ + kernel_execve(name, argv); \ +}) + +#define KERNEL_EXECVE(x, ...) __KERNEL_EXECVE(#x, #x, ##__VA_ARGS__) + +#define KERNEL_EXECVE2(x, ...) KERNEL_EXECVE(x, ##__VA_ARGS__) + +#define __KERNEL_EXECVE3(x, s, ...) KERNEL_EXECVE(x, #s, ##__VA_ARGS__) + +#define KERNEL_EXECVE3(x, s, ...) __KERNEL_EXECVE3(x, s, ##__VA_ARGS__) + +// user_main - kernel thread used to exec a user program +static int +user_main(void *arg) { +#ifdef TEST +#ifdef TESTSCRIPT + KERNEL_EXECVE3(TEST, TESTSCRIPT); +#else + KERNEL_EXECVE2(TEST); +#endif +#else + KERNEL_EXECVE(sh); +#endif + panic("user_main execve failed.\n"); +} + +// init_main - the second kernel thread used to create user_main kernel threads +static int +init_main(void *arg) { + int ret; + if ((ret = vfs_set_bootfs("disk0:")) != 0) { + panic("set boot fs failed: %e.\n", ret); + } + + size_t nr_free_pages_store = nr_free_pages(); + size_t slab_allocated_store = kallocated(); + + int pid = kernel_thread(user_main, NULL, 0); + if (pid <= 0) { + panic("create user_main failed.\n"); + } + extern void check_sync(void); + check_sync(); // check philosopher sync problem + + while (do_wait(0, NULL) == 0) { + schedule(); + } + + fs_cleanup(); + + cprintf("all user-mode processes have quit.\n"); + assert(initproc->cptr == NULL && initproc->yptr == NULL && initproc->optr == NULL); + assert(nr_process == 2); + assert(list_next(&proc_list) == &(initproc->list_link)); + assert(list_prev(&proc_list) == &(initproc->list_link)); + assert(nr_free_pages_store == nr_free_pages()); + assert(slab_allocated_store == kallocated()); + cprintf("init check memory pass.\n"); + return 0; +} + +// proc_init - set up the first kernel thread idleproc "idle" by itself and +// - create the second kernel thread init_main +void +proc_init(void) { + int i; + + list_init(&proc_list); + for (i = 0; i < HASH_LIST_SIZE; i ++) { + list_init(hash_list + i); + } + + if ((idleproc = alloc_proc()) == NULL) { + panic("cannot alloc idleproc.\n"); + } + + idleproc->pid = 0; + idleproc->state = PROC_RUNNABLE; + idleproc->kstack = (uintptr_t)bootstack; + idleproc->need_resched = 1; + + if ((idleproc->filesp = files_create()) == NULL) { + panic("create filesp (idleproc) failed.\n"); + } + files_count_inc(idleproc->filesp); + + set_proc_name(idleproc, "idle"); + nr_process ++; + + current = idleproc; + + int pid = kernel_thread(init_main, NULL, 0); + if (pid <= 0) { + panic("create init_main failed.\n"); + } + + initproc = find_proc(pid); + set_proc_name(initproc, "init"); + + assert(idleproc != NULL && idleproc->pid == 0); + assert(initproc != NULL && initproc->pid == 1); +} + +// cpu_idle - at the end of kern_init, the first kernel thread idleproc will do below works +void +cpu_idle(void) { + while (1) { + if (current->need_resched) { + schedule(); + } + } +} + +//FOR LAB6, set the process's priority (bigger value will get more CPU time) +void +lab6_set_priority(uint32_t priority) +{ + if (priority == 0) + current->lab6_priority = 1; + else current->lab6_priority = priority; +} + +// do_sleep - set current process state to sleep and add timer with "time" +// - then call scheduler. if process run again, delete timer first. +int +do_sleep(unsigned int time) { + if (time == 0) { + return 0; + } + bool intr_flag; + local_intr_save(intr_flag); + timer_t __timer, *timer = timer_init(&__timer, current, time); + current->state = PROC_SLEEPING; + current->wait_state = WT_TIMER; + add_timer(timer); + local_intr_restore(intr_flag); + + schedule(); + + del_timer(timer); + return 0; +} diff --git a/code/lab8/kern/process/proc.h b/code/lab8/kern/process/proc.h new file mode 100644 index 0000000..259245a --- /dev/null +++ b/code/lab8/kern/process/proc.h @@ -0,0 +1,105 @@ +#ifndef __KERN_PROCESS_PROC_H__ +#define __KERN_PROCESS_PROC_H__ + +#include +#include +#include +#include +#include + + +// process's state in his life cycle +enum proc_state { + PROC_UNINIT = 0, // uninitialized + PROC_SLEEPING, // sleeping + PROC_RUNNABLE, // runnable(maybe running) + PROC_ZOMBIE, // almost dead, and wait parent proc to reclaim his resource +}; + +// Saved registers for kernel context switches. +// Don't need to save all the %fs etc. segment registers, +// because they are constant across kernel contexts. +// Save all the regular registers so we don't need to care +// which are caller save, but not the return register %eax. +// (Not saving %eax just simplifies the switching code.) +// The layout of context must match code in switch.S. +struct context { + uint32_t eip; + uint32_t esp; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t esi; + uint32_t edi; + uint32_t ebp; +}; + +#define PROC_NAME_LEN 50 +#define MAX_PROCESS 4096 +#define MAX_PID (MAX_PROCESS * 2) + +extern list_entry_t proc_list; + +struct inode; +struct fs_struct; + +struct proc_struct { + enum proc_state state; // Process state + int pid; // Process ID + int runs; // the running times of Proces + uintptr_t kstack; // Process kernel stack + volatile bool need_resched; // bool value: need to be rescheduled to release CPU? + struct proc_struct *parent; // the parent process + struct mm_struct *mm; // Process's memory management field + struct context context; // Switch here to run process + struct trapframe *tf; // Trap frame for current interrupt + uintptr_t cr3; // CR3 register: the base addr of Page Directroy Table(PDT) + uint32_t flags; // Process flag + char name[PROC_NAME_LEN + 1]; // Process name + list_entry_t list_link; // Process link list + list_entry_t hash_link; // Process hash list + int exit_code; // exit code (be sent to parent proc) + uint32_t wait_state; // waiting state + struct proc_struct *cptr, *yptr, *optr; // relations between processes + struct run_queue *rq; // running queue contains Process + list_entry_t run_link; // the entry linked in run queue + int time_slice; // time slice for occupying the CPU + skew_heap_entry_t lab6_run_pool; // FOR LAB6 ONLY: the entry in the run pool + uint32_t lab6_stride; // FOR LAB6 ONLY: the current stride of the process + uint32_t lab6_priority; // FOR LAB6 ONLY: the priority of process, set by lab6_set_priority(uint32_t) + struct files_struct *filesp; // the file related info(pwd, files_count, files_array, fs_semaphore) of process +}; + +#define PF_EXITING 0x00000001 // getting shutdown + +#define WT_INTERRUPTED 0x80000000 // the wait state could be interrupted +#define WT_CHILD (0x00000001 | WT_INTERRUPTED) // wait child process +#define WT_KSEM 0x00000100 // wait kernel semaphore +#define WT_TIMER (0x00000002 | WT_INTERRUPTED) // wait timer +#define WT_KBD (0x00000004 | WT_INTERRUPTED) // wait the input of keyboard + +#define le2proc(le, member) \ + to_struct((le), struct proc_struct, member) + +extern struct proc_struct *idleproc, *initproc, *current; + +void proc_init(void); +void proc_run(struct proc_struct *proc); +int kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags); + +char *set_proc_name(struct proc_struct *proc, const char *name); +char *get_proc_name(struct proc_struct *proc); +void cpu_idle(void) __attribute__((noreturn)); + +struct proc_struct *find_proc(int pid); +int do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf); +int do_exit(int error_code); +int do_yield(void); +int do_execve(const char *name, int argc, const char **argv); +int do_wait(int pid, int *code_store); +int do_kill(int pid); +//FOR LAB6, set the process's priority (bigger value will get more CPU time) +void lab6_set_priority(uint32_t priority); +int do_sleep(unsigned int time); +#endif /* !__KERN_PROCESS_PROC_H__ */ + diff --git a/code/lab8/kern/process/switch.S b/code/lab8/kern/process/switch.S new file mode 100644 index 0000000..27b4c8c --- /dev/null +++ b/code/lab8/kern/process/switch.S @@ -0,0 +1,30 @@ +.text +.globl switch_to +switch_to: # switch_to(from, to) + + # save from's registers + movl 4(%esp), %eax # eax points to from + popl 0(%eax) # save eip !popl + movl %esp, 4(%eax) + movl %ebx, 8(%eax) + movl %ecx, 12(%eax) + movl %edx, 16(%eax) + movl %esi, 20(%eax) + movl %edi, 24(%eax) + movl %ebp, 28(%eax) + + # restore to's registers + movl 4(%esp), %eax # not 8(%esp): popped return address already + # eax now points to to + movl 28(%eax), %ebp + movl 24(%eax), %edi + movl 20(%eax), %esi + movl 16(%eax), %edx + movl 12(%eax), %ecx + movl 8(%eax), %ebx + movl 4(%eax), %esp + + pushl 0(%eax) # push eip + + ret + diff --git a/code/lab8/kern/schedule/default_sched.c b/code/lab8/kern/schedule/default_sched.c new file mode 100644 index 0000000..2316990 --- /dev/null +++ b/code/lab8/kern/schedule/default_sched.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +static void +RR_init(struct run_queue *rq) { + list_init(&(rq->run_list)); + rq->proc_num = 0; +} + +static void +RR_enqueue(struct run_queue *rq, struct proc_struct *proc) { + assert(list_empty(&(proc->run_link))); + list_add_before(&(rq->run_list), &(proc->run_link)); + if (proc->time_slice == 0 || proc->time_slice > rq->max_time_slice) { + proc->time_slice = rq->max_time_slice; + } + proc->rq = rq; + rq->proc_num ++; +} + +static void +RR_dequeue(struct run_queue *rq, struct proc_struct *proc) { + assert(!list_empty(&(proc->run_link)) && proc->rq == rq); + list_del_init(&(proc->run_link)); + rq->proc_num --; +} + +static struct proc_struct * +RR_pick_next(struct run_queue *rq) { + list_entry_t *le = list_next(&(rq->run_list)); + if (le != &(rq->run_list)) { + return le2proc(le, run_link); + } + return NULL; +} + +static void +RR_proc_tick(struct run_queue *rq, struct proc_struct *proc) { + if (proc->time_slice > 0) { + proc->time_slice --; + } + if (proc->time_slice == 0) { + proc->need_resched = 1; + } +} + +struct sched_class default_sched_class = { + .name = "RR_scheduler", + .init = RR_init, + .enqueue = RR_enqueue, + .dequeue = RR_dequeue, + .pick_next = RR_pick_next, + .proc_tick = RR_proc_tick, +}; + diff --git a/code/lab8/kern/schedule/default_sched.h b/code/lab8/kern/schedule/default_sched.h new file mode 100644 index 0000000..2f21fbd --- /dev/null +++ b/code/lab8/kern/schedule/default_sched.h @@ -0,0 +1,9 @@ +#ifndef __KERN_SCHEDULE_SCHED_RR_H__ +#define __KERN_SCHEDULE_SCHED_RR_H__ + +#include + +extern struct sched_class default_sched_class; + +#endif /* !__KERN_SCHEDULE_SCHED_RR_H__ */ + diff --git a/code/lab8/kern/schedule/default_sched_stride_c b/code/lab8/kern/schedule/default_sched_stride_c new file mode 100644 index 0000000..7075653 --- /dev/null +++ b/code/lab8/kern/schedule/default_sched_stride_c @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include + +#define USE_SKEW_HEAP 1 + +/* You should define the BigStride constant here*/ +/* LAB6: YOUR CODE */ +#define BIG_STRIDE /* you should give a value, and is ??? */ + +/* The compare function for two skew_heap_node_t's and the + * corresponding procs*/ +static int +proc_stride_comp_f(void *a, void *b) +{ + struct proc_struct *p = le2proc(a, lab6_run_pool); + struct proc_struct *q = le2proc(b, lab6_run_pool); + int32_t c = p->lab6_stride - q->lab6_stride; + if (c > 0) return 1; + else if (c == 0) return 0; + else return -1; +} + +/* + * stride_init initializes the run-queue rq with correct assignment for + * member variables, including: + * + * - run_list: should be a empty list after initialization. + * - lab6_run_pool: NULL + * - proc_num: 0 + * - max_time_slice: no need here, the variable would be assigned by the caller. + * + * hint: see libs/list.h for routines of the list structures. + */ +static void +stride_init(struct run_queue *rq) { + /* LAB6: YOUR CODE + * (1) init the ready process list: rq->run_list + * (2) init the run pool: rq->lab6_run_pool + * (3) set number of process: rq->proc_num to 0 + */ +} + +/* + * stride_enqueue inserts the process ``proc'' into the run-queue + * ``rq''. The procedure should verify/initialize the relevant members + * of ``proc'', and then put the ``lab6_run_pool'' node into the + * queue(since we use priority queue here). The procedure should also + * update the meta date in ``rq'' structure. + * + * proc->time_slice denotes the time slices allocation for the + * process, which should set to rq->max_time_slice. + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static void +stride_enqueue(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE + * (1) insert the proc into rq correctly + * NOTICE: you can use skew_heap or list. Important functions + * skew_heap_insert: insert a entry into skew_heap + * list_add_before: insert a entry into the last of list + * (2) recalculate proc->time_slice + * (3) set proc->rq pointer to rq + * (4) increase rq->proc_num + */ +} + +/* + * stride_dequeue removes the process ``proc'' from the run-queue + * ``rq'', the operation would be finished by the skew_heap_remove + * operations. Remember to update the ``rq'' structure. + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static void +stride_dequeue(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE + * (1) remove the proc from rq correctly + * NOTICE: you can use skew_heap or list. Important functions + * skew_heap_remove: remove a entry from skew_heap + * list_del_init: remove a entry from the list + */ +} +/* + * stride_pick_next pick the element from the ``run-queue'', with the + * minimum value of stride, and returns the corresponding process + * pointer. The process pointer would be calculated by macro le2proc, + * see kern/process/proc.h for definition. Return NULL if + * there is no process in the queue. + * + * When one proc structure is selected, remember to update the stride + * property of the proc. (stride += BIG_STRIDE / priority) + * + * hint: see libs/skew_heap.h for routines of the priority + * queue structures. + */ +static struct proc_struct * +stride_pick_next(struct run_queue *rq) { + /* LAB6: YOUR CODE + * (1) get a proc_struct pointer p with the minimum value of stride + (1.1) If using skew_heap, we can use le2proc get the p from rq->lab6_run_poll + (1.2) If using list, we have to search list to find the p with minimum stride value + * (2) update p;s stride value: p->lab6_stride + * (3) return p + */ +} + +/* + * stride_proc_tick works with the tick event of current process. You + * should check whether the time slices for current process is + * exhausted and update the proc struct ``proc''. proc->time_slice + * denotes the time slices left for current + * process. proc->need_resched is the flag variable for process + * switching. + */ +static void +stride_proc_tick(struct run_queue *rq, struct proc_struct *proc) { + /* LAB6: YOUR CODE */ +} + +struct sched_class default_sched_class = { + .name = "stride_scheduler", + .init = stride_init, + .enqueue = stride_enqueue, + .dequeue = stride_dequeue, + .pick_next = stride_pick_next, + .proc_tick = stride_proc_tick, +}; diff --git a/code/lab8/kern/schedule/sched.c b/code/lab8/kern/schedule/sched.c new file mode 100644 index 0000000..e272635 --- /dev/null +++ b/code/lab8/kern/schedule/sched.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include + +static list_entry_t timer_list; + +static struct sched_class *sched_class; + +static struct run_queue *rq; + +static inline void +sched_class_enqueue(struct proc_struct *proc) { + if (proc != idleproc) { + sched_class->enqueue(rq, proc); + } +} + +static inline void +sched_class_dequeue(struct proc_struct *proc) { + sched_class->dequeue(rq, proc); +} + +static inline struct proc_struct * +sched_class_pick_next(void) { + return sched_class->pick_next(rq); +} + +static void +sched_class_proc_tick(struct proc_struct *proc) { + if (proc != idleproc) { + sched_class->proc_tick(rq, proc); + } + else { + proc->need_resched = 1; + } +} + +static struct run_queue __rq; + +void +sched_init(void) { + list_init(&timer_list); + + sched_class = &default_sched_class; + + rq = &__rq; + rq->max_time_slice = 20; + sched_class->init(rq); + + cprintf("sched class: %s\n", sched_class->name); +} + +void +wakeup_proc(struct proc_struct *proc) { + assert(proc->state != PROC_ZOMBIE); + bool intr_flag; + local_intr_save(intr_flag); + { + if (proc->state != PROC_RUNNABLE) { + proc->state = PROC_RUNNABLE; + proc->wait_state = 0; + if (proc != current) { + sched_class_enqueue(proc); + } + } + else { + warn("wakeup runnable process.\n"); + } + } + local_intr_restore(intr_flag); +} + +void +schedule(void) { + bool intr_flag; + struct proc_struct *next; + local_intr_save(intr_flag); + { + current->need_resched = 0; + if (current->state == PROC_RUNNABLE) { + sched_class_enqueue(current); + } + if ((next = sched_class_pick_next()) != NULL) { + sched_class_dequeue(next); + } + if (next == NULL) { + next = idleproc; + } + next->runs ++; + if (next != current) { + proc_run(next); + } + } + local_intr_restore(intr_flag); +} + +void +add_timer(timer_t *timer) { + bool intr_flag; + local_intr_save(intr_flag); + { + assert(timer->expires > 0 && timer->proc != NULL); + assert(list_empty(&(timer->timer_link))); + list_entry_t *le = list_next(&timer_list); + while (le != &timer_list) { + timer_t *next = le2timer(le, timer_link); + if (timer->expires < next->expires) { + next->expires -= timer->expires; + break; + } + timer->expires -= next->expires; + le = list_next(le); + } + list_add_before(le, &(timer->timer_link)); + } + local_intr_restore(intr_flag); +} + +void +del_timer(timer_t *timer) { + bool intr_flag; + local_intr_save(intr_flag); + { + if (!list_empty(&(timer->timer_link))) { + if (timer->expires != 0) { + list_entry_t *le = list_next(&(timer->timer_link)); + if (le != &timer_list) { + timer_t *next = le2timer(le, timer_link); + next->expires += timer->expires; + } + } + list_del_init(&(timer->timer_link)); + } + } + local_intr_restore(intr_flag); +} + +void +run_timer_list(void) { + bool intr_flag; + local_intr_save(intr_flag); + { + list_entry_t *le = list_next(&timer_list); + if (le != &timer_list) { + timer_t *timer = le2timer(le, timer_link); + assert(timer->expires != 0); + timer->expires --; + while (timer->expires == 0) { + le = list_next(le); + struct proc_struct *proc = timer->proc; + if (proc->wait_state != 0) { + assert(proc->wait_state & WT_INTERRUPTED); + } + else { + warn("process %d's wait_state == 0.\n", proc->pid); + } + wakeup_proc(proc); + del_timer(timer); + if (le == &timer_list) { + break; + } + timer = le2timer(le, timer_link); + } + } + sched_class_proc_tick(current); + } + local_intr_restore(intr_flag); +} diff --git a/code/lab8/kern/schedule/sched.h b/code/lab8/kern/schedule/sched.h new file mode 100644 index 0000000..c83a776 --- /dev/null +++ b/code/lab8/kern/schedule/sched.h @@ -0,0 +1,70 @@ +#ifndef __KERN_SCHEDULE_SCHED_H__ +#define __KERN_SCHEDULE_SCHED_H__ + +#include +#include +#include + +struct proc_struct; + +typedef struct { + unsigned int expires; + struct proc_struct *proc; + list_entry_t timer_link; +} timer_t; + +#define le2timer(le, member) \ +to_struct((le), timer_t, member) + +static inline timer_t * +timer_init(timer_t *timer, struct proc_struct *proc, int expires) { + timer->expires = expires; + timer->proc = proc; + list_init(&(timer->timer_link)); + return timer; +} + +struct run_queue; + +// The introduction of scheduling classes is borrrowed from Linux, and makes the +// core scheduler quite extensible. These classes (the scheduler modules) encapsulate +// the scheduling policies. +struct sched_class { + // the name of sched_class + const char *name; + // Init the run queue + void (*init)(struct run_queue *rq); + // put the proc into runqueue, and this function must be called with rq_lock + void (*enqueue)(struct run_queue *rq, struct proc_struct *proc); + // get the proc out runqueue, and this function must be called with rq_lock + void (*dequeue)(struct run_queue *rq, struct proc_struct *proc); + // choose the next runnable task + struct proc_struct *(*pick_next)(struct run_queue *rq); + // dealer of the time-tick + void (*proc_tick)(struct run_queue *rq, struct proc_struct *proc); + /* for SMP support in the future + * load_balance + * void (*load_balance)(struct rq* rq); + * get some proc from this rq, used in load_balance, + * return value is the num of gotten proc + * int (*get_proc)(struct rq* rq, struct proc* procs_moved[]); + */ +}; + +struct run_queue { + list_entry_t run_list; + unsigned int proc_num; + int max_time_slice; + // For LAB6 ONLY + skew_heap_entry_t *lab6_run_pool; +}; + +void sched_init(void); +void wakeup_proc(struct proc_struct *proc); +void schedule(void); +void add_timer(timer_t *timer); +void del_timer(timer_t *timer); +void run_timer_list(void); + +#endif /* !__KERN_SCHEDULE_SCHED_H__ */ + diff --git a/code/lab8/kern/sync/check_sync.c b/code/lab8/kern/sync/check_sync.c new file mode 100644 index 0000000..9d48940 --- /dev/null +++ b/code/lab8/kern/sync/check_sync.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include + +#define N 5 /* 哲学家数目 */ +#define LEFT (i-1+N)%N /* i的左邻号码 */ +#define RIGHT (i+1)%N /* i的右邻号码 */ +#define THINKING 0 /* 哲学家正在思考 */ +#define HUNGRY 1 /* 哲学家想取得叉子 */ +#define EATING 2 /* 哲学家正在吃面 */ +#define TIMES 4 /* 吃4次饭 */ +#define SLEEP_TIME 10 + +//---------- philosophers problem using semaphore ---------------------- +int state_sema[N]; /* 记录每个人状态的数组 */ +/* 信号量是一个特殊的整型变量 */ +semaphore_t mutex; /* 临界区互斥 */ +semaphore_t s[N]; /* 每个哲学家一个信号量 */ + +struct proc_struct *philosopher_proc_sema[N]; + +void phi_test_sema(i) /* i:哲学家号码从0到N-1 */ +{ + if(state_sema[i]==HUNGRY&&state_sema[LEFT]!=EATING + &&state_sema[RIGHT]!=EATING) + { + state_sema[i]=EATING; + up(&s[i]); + } +} + +void phi_take_forks_sema(int i) /* i:哲学家号码从0到N-1 */ +{ + down(&mutex); /* 进入临界区 */ + state_sema[i]=HUNGRY; /* 记录下哲学家i饥饿的事实 */ + phi_test_sema(i); /* 试图得到两只叉子 */ + up(&mutex); /* 离开临界区 */ + down(&s[i]); /* 如果得不到叉子就阻塞 */ +} + +void phi_put_forks_sema(int i) /* i:哲学家号码从0到N-1 */ +{ + down(&mutex); /* 进入临界区 */ + state_sema[i]=THINKING; /* 哲学家进餐结束 */ + phi_test_sema(LEFT); /* 看一下左邻居现在是否能进餐 */ + phi_test_sema(RIGHT); /* 看一下右邻居现在是否能进餐 */ + up(&mutex); /* 离开临界区 */ +} + +int philosopher_using_semaphore(void * arg) /* i:哲学家号码,从0到N-1 */ +{ + int i, iter=0; + i=(int)arg; + cprintf("I am No.%d philosopher_sema\n",i); + while(iter++cv[i]) ; + } +} + + +void phi_take_forks_condvar(int i) { + down(&(mtp->mutex)); +//--------into routine in monitor-------------- + // LAB7 EXERCISE1: YOUR CODE + // I am hungry + // try to get fork +//--------leave routine in monitor-------------- + if(mtp->next_count>0) + up(&(mtp->next)); + else + up(&(mtp->mutex)); +} + +void phi_put_forks_condvar(int i) { + down(&(mtp->mutex)); + +//--------into routine in monitor-------------- + // LAB7 EXERCISE1: YOUR CODE + // I ate over + // test left and right neighbors +//--------leave routine in monitor-------------- + if(mtp->next_count>0) + up(&(mtp->next)); + else + up(&(mtp->mutex)); +} + +//---------- philosophers using monitor (condition variable) ---------------------- +int philosopher_using_condvar(void * arg) { /* arg is the No. of philosopher 0~N-1*/ + + int i, iter=0; + i=(int)arg; + cprintf("I am No.%d philosopher_condvar\n",i); + while(iter++ +#include +#include +#include + + +// Initialize monitor. +void +monitor_init (monitor_t * mtp, size_t num_cv) { + int i; + assert(num_cv>0); + mtp->next_count = 0; + mtp->cv = NULL; + sem_init(&(mtp->mutex), 1); //unlocked + sem_init(&(mtp->next), 0); + mtp->cv =(condvar_t *) kmalloc(sizeof(condvar_t)*num_cv); + assert(mtp->cv!=NULL); + for(i=0; icv[i].count=0; + sem_init(&(mtp->cv[i].sem),0); + mtp->cv[i].owner=mtp; + } +} + +// Unlock one of threads waiting on the condition variable. +void +cond_signal (condvar_t *cvp) { + //LAB7 EXERCISE1: YOUR CODE + cprintf("cond_signal begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); + /* + * cond_signal(cv) { + * if(cv.count>0) { + * mt.next_count ++; + * signal(cv.sem); + * wait(mt.next); + * mt.next_count--; + * } + * } + */ + cprintf("cond_signal end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); +} + +// Suspend calling thread on a condition variable waiting for condition Atomically unlocks +// mutex and suspends calling thread on conditional variable after waking up locks mutex. Notice: mp is mutex semaphore for monitor's procedures +void +cond_wait (condvar_t *cvp) { + //LAB7 EXERCISE1: YOUR CODE + cprintf("cond_wait begin: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); + /* + * cv.count ++; + * if(mt.next_count>0) + * signal(mt.next) + * else + * signal(mt.mutex); + * wait(cv.sem); + * cv.count --; + */ + cprintf("cond_wait end: cvp %x, cvp->count %d, cvp->owner->next_count %d\n", cvp, cvp->count, cvp->owner->next_count); +} diff --git a/code/lab8/kern/sync/monitor.h b/code/lab8/kern/sync/monitor.h new file mode 100644 index 0000000..39b9610 --- /dev/null +++ b/code/lab8/kern/sync/monitor.h @@ -0,0 +1,90 @@ +#ifndef __KERN_SYNC_MONITOR_CONDVAR_H__ +#define __KERN_SYNC_MOINTOR_CONDVAR_H__ + +#include +/* In [OS CONCEPT] 7.7 section, the accurate define and approximate implementation of MONITOR was introduced. + * INTRODUCTION: + * Monitors were invented by C. A. R. Hoare and Per Brinch Hansen, and were first implemented in Brinch Hansen's + * Concurrent Pascal language. Generally, a monitor is a language construct and the compiler usually enforces mutual exclusion. Compare this with semaphores, which are usually an OS construct. + * DEFNIE & CHARACTERISTIC: + * A monitor is a collection of procedures, variables, and data structures grouped together. + * Processes can call the monitor procedures but cannot access the internal data structures. + * Only one process at a time may be be active in a monitor. + * Condition variables allow for blocking and unblocking. + * cv.wait() blocks a process. + * The process is said to be waiting for (or waiting on) the condition variable cv. + * cv.signal() (also called cv.notify) unblocks a process waiting for the condition variable cv. + * When this occurs, we need to still require that only one process is active in the monitor. This can be done in several ways: + * on some systems the old process (the one executing the signal) leaves the monitor and the new one enters + * on some systems the signal must be the last statement executed inside the monitor. + * on some systems the old process will block until the monitor is available again. + * on some systems the new process (the one unblocked by the signal) will remain blocked until the monitor is available again. + * If a condition variable is signaled with nobody waiting, the signal is lost. Compare this with semaphores, in which a signal will allow a process that executes a wait in the future to no block. + * You should not think of a condition variable as a variable in the traditional sense. + * It does not have a value. + * Think of it as an object in the OOP sense. + * It has two methods, wait and signal that manipulate the calling process. + * IMPLEMENTATION: + * monitor mt { + * ----------------variable------------------ + * semaphore mutex; + * semaphore next; + * int next_count; + * condvar {int count, sempahore sem} cv[N]; + * other variables in mt; + * --------condvar wait/signal--------------- + * cond_wait (cv) { + * cv.count ++; + * if(mt.next_count>0) + * signal(mt.next) + * else + * signal(mt.mutex); + * wait(cv.sem); + * cv.count --; + * } + * + * cond_signal(cv) { + * if(cv.count>0) { + * mt.next_count ++; + * signal(cv.sem); + * wait(mt.next); + * mt.next_count--; + * } + * } + * --------routines in monitor--------------- + * routineA_in_mt () { + * wait(mt.mutex); + * ... + * real body of routineA + * ... + * if(next_count>0) + * signal(mt.next); + * else + * signal(mt.mutex); + * } + */ + +typedef struct monitor monitor_t; + +typedef struct condvar{ + semaphore_t sem; // the sem semaphore is used to down the waiting proc, and the signaling proc should up the waiting proc + int count; // the number of waiters on condvar + monitor_t * owner; // the owner(monitor) of this condvar +} condvar_t; + +typedef struct monitor{ + semaphore_t mutex; // the mutex lock for going into the routines in monitor, should be initialized to 1 + semaphore_t next; // the next semaphore is used to down the signaling proc itself, and the other OR wakeuped waiting proc should wake up the sleeped signaling proc. + int next_count; // the number of of sleeped signaling proc + condvar_t *cv; // the condvars in monitor +} monitor_t; + +// Initialize variables in monitor. +void monitor_init (monitor_t *cvp, size_t num_cv); +// Unlock one of threads waiting on the condition variable. +void cond_signal (condvar_t *cvp); +// Suspend calling thread on a condition variable waiting for condition atomically unlock mutex in monitor, +// and suspends calling thread on conditional variable after waking up locks mutex. +void cond_wait (condvar_t *cvp); + +#endif /* !__KERN_SYNC_MONITOR_CONDVAR_H__ */ diff --git a/code/lab8/kern/sync/sem.c b/code/lab8/kern/sync/sem.c new file mode 100644 index 0000000..62c81db --- /dev/null +++ b/code/lab8/kern/sync/sem.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +void +sem_init(semaphore_t *sem, int value) { + sem->value = value; + wait_queue_init(&(sem->wait_queue)); +} + +static __noinline void __up(semaphore_t *sem, uint32_t wait_state) { + bool intr_flag; + local_intr_save(intr_flag); + { + wait_t *wait; + if ((wait = wait_queue_first(&(sem->wait_queue))) == NULL) { + sem->value ++; + } + else { + assert(wait->proc->wait_state == wait_state); + wakeup_wait(&(sem->wait_queue), wait, wait_state, 1); + } + } + local_intr_restore(intr_flag); +} + +static __noinline uint32_t __down(semaphore_t *sem, uint32_t wait_state) { + bool intr_flag; + local_intr_save(intr_flag); + if (sem->value > 0) { + sem->value --; + local_intr_restore(intr_flag); + return 0; + } + wait_t __wait, *wait = &__wait; + wait_current_set(&(sem->wait_queue), wait, wait_state); + local_intr_restore(intr_flag); + + schedule(); + + local_intr_save(intr_flag); + wait_current_del(&(sem->wait_queue), wait); + local_intr_restore(intr_flag); + + if (wait->wakeup_flags != wait_state) { + return wait->wakeup_flags; + } + return 0; +} + +void +up(semaphore_t *sem) { + __up(sem, WT_KSEM); +} + +void +down(semaphore_t *sem) { + uint32_t flags = __down(sem, WT_KSEM); + assert(flags == 0); +} + +bool +try_down(semaphore_t *sem) { + bool intr_flag, ret = 0; + local_intr_save(intr_flag); + if (sem->value > 0) { + sem->value --, ret = 1; + } + local_intr_restore(intr_flag); + return ret; +} + diff --git a/code/lab8/kern/sync/sem.h b/code/lab8/kern/sync/sem.h new file mode 100644 index 0000000..4450fe7 --- /dev/null +++ b/code/lab8/kern/sync/sem.h @@ -0,0 +1,19 @@ +#ifndef __KERN_SYNC_SEM_H__ +#define __KERN_SYNC_SEM_H__ + +#include +#include +#include + +typedef struct { + int value; + wait_queue_t wait_queue; +} semaphore_t; + +void sem_init(semaphore_t *sem, int value); +void up(semaphore_t *sem); +void down(semaphore_t *sem); +bool try_down(semaphore_t *sem); + +#endif /* !__KERN_SYNC_SEM_H__ */ + diff --git a/code/lab8/kern/sync/sync.h b/code/lab8/kern/sync/sync.h new file mode 100644 index 0000000..98215f1 --- /dev/null +++ b/code/lab8/kern/sync/sync.h @@ -0,0 +1,31 @@ +#ifndef __KERN_SYNC_SYNC_H__ +#define __KERN_SYNC_SYNC_H__ + +#include +#include +#include +#include +#include +#include + +static inline bool +__intr_save(void) { + if (read_eflags() & FL_IF) { + intr_disable(); + return 1; + } + return 0; +} + +static inline void +__intr_restore(bool flag) { + if (flag) { + intr_enable(); + } +} + +#define local_intr_save(x) do { x = __intr_save(); } while (0) +#define local_intr_restore(x) __intr_restore(x); + +#endif /* !__KERN_SYNC_SYNC_H__ */ + diff --git a/code/lab8/kern/sync/wait.c b/code/lab8/kern/sync/wait.c new file mode 100644 index 0000000..6aea172 --- /dev/null +++ b/code/lab8/kern/sync/wait.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +void +wait_init(wait_t *wait, struct proc_struct *proc) { + wait->proc = proc; + wait->wakeup_flags = WT_INTERRUPTED; + list_init(&(wait->wait_link)); +} + +void +wait_queue_init(wait_queue_t *queue) { + list_init(&(queue->wait_head)); +} + +void +wait_queue_add(wait_queue_t *queue, wait_t *wait) { + assert(list_empty(&(wait->wait_link)) && wait->proc != NULL); + wait->wait_queue = queue; + list_add_before(&(queue->wait_head), &(wait->wait_link)); +} + +void +wait_queue_del(wait_queue_t *queue, wait_t *wait) { + assert(!list_empty(&(wait->wait_link)) && wait->wait_queue == queue); + list_del_init(&(wait->wait_link)); +} + +wait_t * +wait_queue_next(wait_queue_t *queue, wait_t *wait) { + assert(!list_empty(&(wait->wait_link)) && wait->wait_queue == queue); + list_entry_t *le = list_next(&(wait->wait_link)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +wait_t * +wait_queue_prev(wait_queue_t *queue, wait_t *wait) { + assert(!list_empty(&(wait->wait_link)) && wait->wait_queue == queue); + list_entry_t *le = list_prev(&(wait->wait_link)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +wait_t * +wait_queue_first(wait_queue_t *queue) { + list_entry_t *le = list_next(&(queue->wait_head)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +wait_t * +wait_queue_last(wait_queue_t *queue) { + list_entry_t *le = list_prev(&(queue->wait_head)); + if (le != &(queue->wait_head)) { + return le2wait(le, wait_link); + } + return NULL; +} + +bool +wait_queue_empty(wait_queue_t *queue) { + return list_empty(&(queue->wait_head)); +} + +bool +wait_in_queue(wait_t *wait) { + return !list_empty(&(wait->wait_link)); +} + +void +wakeup_wait(wait_queue_t *queue, wait_t *wait, uint32_t wakeup_flags, bool del) { + if (del) { + wait_queue_del(queue, wait); + } + wait->wakeup_flags = wakeup_flags; + wakeup_proc(wait->proc); +} + +void +wakeup_first(wait_queue_t *queue, uint32_t wakeup_flags, bool del) { + wait_t *wait; + if ((wait = wait_queue_first(queue)) != NULL) { + wakeup_wait(queue, wait, wakeup_flags, del); + } +} + +void +wakeup_queue(wait_queue_t *queue, uint32_t wakeup_flags, bool del) { + wait_t *wait; + if ((wait = wait_queue_first(queue)) != NULL) { + if (del) { + do { + wakeup_wait(queue, wait, wakeup_flags, 1); + } while ((wait = wait_queue_first(queue)) != NULL); + } + else { + do { + wakeup_wait(queue, wait, wakeup_flags, 0); + } while ((wait = wait_queue_next(queue, wait)) != NULL); + } + } +} + +void +wait_current_set(wait_queue_t *queue, wait_t *wait, uint32_t wait_state) { + assert(current != NULL); + wait_init(wait, current); + current->state = PROC_SLEEPING; + current->wait_state = wait_state; + wait_queue_add(queue, wait); +} + diff --git a/code/lab8/kern/sync/wait.h b/code/lab8/kern/sync/wait.h new file mode 100644 index 0000000..46758b7 --- /dev/null +++ b/code/lab8/kern/sync/wait.h @@ -0,0 +1,48 @@ +#ifndef __KERN_SYNC_WAIT_H__ +#define __KERN_SYNC_WAIT_H__ + +#include + +typedef struct { + list_entry_t wait_head; +} wait_queue_t; + +struct proc_struct; + +typedef struct { + struct proc_struct *proc; + uint32_t wakeup_flags; + wait_queue_t *wait_queue; + list_entry_t wait_link; +} wait_t; + +#define le2wait(le, member) \ + to_struct((le), wait_t, member) + +void wait_init(wait_t *wait, struct proc_struct *proc); +void wait_queue_init(wait_queue_t *queue); +void wait_queue_add(wait_queue_t *queue, wait_t *wait); +void wait_queue_del(wait_queue_t *queue, wait_t *wait); + +wait_t *wait_queue_next(wait_queue_t *queue, wait_t *wait); +wait_t *wait_queue_prev(wait_queue_t *queue, wait_t *wait); +wait_t *wait_queue_first(wait_queue_t *queue); +wait_t *wait_queue_last(wait_queue_t *queue); + +bool wait_queue_empty(wait_queue_t *queue); +bool wait_in_queue(wait_t *wait); +void wakeup_wait(wait_queue_t *queue, wait_t *wait, uint32_t wakeup_flags, bool del); +void wakeup_first(wait_queue_t *queue, uint32_t wakeup_flags, bool del); +void wakeup_queue(wait_queue_t *queue, uint32_t wakeup_flags, bool del); + +void wait_current_set(wait_queue_t *queue, wait_t *wait, uint32_t wait_state); + +#define wait_current_del(queue, wait) \ + do { \ + if (wait_in_queue(wait)) { \ + wait_queue_del(queue, wait); \ + } \ + } while (0) + +#endif /* !__KERN_SYNC_WAIT_H__ */ + diff --git a/code/lab8/kern/syscall/syscall.c b/code/lab8/kern/syscall/syscall.c new file mode 100644 index 0000000..673b5eb --- /dev/null +++ b/code/lab8/kern/syscall/syscall.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +sys_exit(uint32_t arg[]) { + int error_code = (int)arg[0]; + return do_exit(error_code); +} + +static int +sys_fork(uint32_t arg[]) { + struct trapframe *tf = current->tf; + uintptr_t stack = tf->tf_esp; + return do_fork(0, stack, tf); +} + +static int +sys_wait(uint32_t arg[]) { + int pid = (int)arg[0]; + int *store = (int *)arg[1]; + return do_wait(pid, store); +} + +static int +sys_exec(uint32_t arg[]) { + const char *name = (const char *)arg[0]; + int argc = (int)arg[1]; + const char **argv = (const char **)arg[2]; + return do_execve(name, argc, argv); +} + +static int +sys_yield(uint32_t arg[]) { + return do_yield(); +} + +static int +sys_kill(uint32_t arg[]) { + int pid = (int)arg[0]; + return do_kill(pid); +} + +static int +sys_getpid(uint32_t arg[]) { + return current->pid; +} + +static int +sys_putc(uint32_t arg[]) { + int c = (int)arg[0]; + cputchar(c); + return 0; +} + +static int +sys_pgdir(uint32_t arg[]) { + print_pgdir(); + return 0; +} + +static uint32_t +sys_gettime(uint32_t arg[]) { + return (int)ticks; +} +static uint32_t +sys_lab6_set_priority(uint32_t arg[]) +{ + uint32_t priority = (uint32_t)arg[0]; + lab6_set_priority(priority); + return 0; +} + +static int +sys_sleep(uint32_t arg[]) { + unsigned int time = (unsigned int)arg[0]; + return do_sleep(time); +} + +static int +sys_open(uint32_t arg[]) { + const char *path = (const char *)arg[0]; + uint32_t open_flags = (uint32_t)arg[1]; + return sysfile_open(path, open_flags); +} + +static int +sys_close(uint32_t arg[]) { + int fd = (int)arg[0]; + return sysfile_close(fd); +} + +static int +sys_read(uint32_t arg[]) { + int fd = (int)arg[0]; + void *base = (void *)arg[1]; + size_t len = (size_t)arg[2]; + return sysfile_read(fd, base, len); +} + +static int +sys_write(uint32_t arg[]) { + int fd = (int)arg[0]; + void *base = (void *)arg[1]; + size_t len = (size_t)arg[2]; + return sysfile_write(fd, base, len); +} + +static int +sys_seek(uint32_t arg[]) { + int fd = (int)arg[0]; + off_t pos = (off_t)arg[1]; + int whence = (int)arg[2]; + return sysfile_seek(fd, pos, whence); +} + +static int +sys_fstat(uint32_t arg[]) { + int fd = (int)arg[0]; + struct stat *stat = (struct stat *)arg[1]; + return sysfile_fstat(fd, stat); +} + +static int +sys_fsync(uint32_t arg[]) { + int fd = (int)arg[0]; + return sysfile_fsync(fd); +} + +static int +sys_getcwd(uint32_t arg[]) { + char *buf = (char *)arg[0]; + size_t len = (size_t)arg[1]; + return sysfile_getcwd(buf, len); +} + +static int +sys_getdirentry(uint32_t arg[]) { + int fd = (int)arg[0]; + struct dirent *direntp = (struct dirent *)arg[1]; + return sysfile_getdirentry(fd, direntp); +} + +static int +sys_dup(uint32_t arg[]) { + int fd1 = (int)arg[0]; + int fd2 = (int)arg[1]; + return sysfile_dup(fd1, fd2); +} + +static int (*syscalls[])(uint32_t arg[]) = { + [SYS_exit] sys_exit, + [SYS_fork] sys_fork, + [SYS_wait] sys_wait, + [SYS_exec] sys_exec, + [SYS_yield] sys_yield, + [SYS_kill] sys_kill, + [SYS_getpid] sys_getpid, + [SYS_putc] sys_putc, + [SYS_pgdir] sys_pgdir, + [SYS_gettime] sys_gettime, + [SYS_lab6_set_priority] sys_lab6_set_priority, + [SYS_sleep] sys_sleep, + [SYS_open] sys_open, + [SYS_close] sys_close, + [SYS_read] sys_read, + [SYS_write] sys_write, + [SYS_seek] sys_seek, + [SYS_fstat] sys_fstat, + [SYS_fsync] sys_fsync, + [SYS_getcwd] sys_getcwd, + [SYS_getdirentry] sys_getdirentry, + [SYS_dup] sys_dup, +}; + +#define NUM_SYSCALLS ((sizeof(syscalls)) / (sizeof(syscalls[0]))) + +void +syscall(void) { + struct trapframe *tf = current->tf; + uint32_t arg[5]; + int num = tf->tf_regs.reg_eax; + if (num >= 0 && num < NUM_SYSCALLS) { + if (syscalls[num] != NULL) { + arg[0] = tf->tf_regs.reg_edx; + arg[1] = tf->tf_regs.reg_ecx; + arg[2] = tf->tf_regs.reg_ebx; + arg[3] = tf->tf_regs.reg_edi; + arg[4] = tf->tf_regs.reg_esi; + tf->tf_regs.reg_eax = syscalls[num](arg); + return ; + } + } + print_trapframe(tf); + panic("undefined syscall %d, pid = %d, name = %s.\n", + num, current->pid, current->name); +} + diff --git a/code/lab8/kern/syscall/syscall.h b/code/lab8/kern/syscall/syscall.h new file mode 100644 index 0000000..a8fe843 --- /dev/null +++ b/code/lab8/kern/syscall/syscall.h @@ -0,0 +1,7 @@ +#ifndef __KERN_SYSCALL_SYSCALL_H__ +#define __KERN_SYSCALL_SYSCALL_H__ + +void syscall(void); + +#endif /* !__KERN_SYSCALL_SYSCALL_H__ */ + diff --git a/code/lab8/kern/trap/trap.c b/code/lab8/kern/trap/trap.c new file mode 100644 index 0000000..e8eb143 --- /dev/null +++ b/code/lab8/kern/trap/trap.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TICK_NUM 100 + +static void print_ticks() { + cprintf("%d ticks\n",TICK_NUM); +#ifdef DEBUG_GRADE + cprintf("End of Test.\n"); + panic("EOT: kernel seems ok."); +#endif +} + +/* * + * Interrupt descriptor table: + * + * Must be built at run time because shifted function addresses can't + * be represented in relocation records. + * */ +static struct gatedesc idt[256] = {{0}}; + +static struct pseudodesc idt_pd = { + sizeof(idt) - 1, (uintptr_t)idt +}; + +/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */ +void +idt_init(void) { + /* LAB1 YOUR CODE : STEP 2 */ + /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)? + * All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ? + * __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c + * (try "make" command in lab1, then you will find vector.S in kern/trap DIR) + * You can use "extern uintptr_t __vectors[];" to define this extern variable which will be used later. + * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT). + * Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT + * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction. + * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. + * Notice: the argument of lidt is idt_pd. try to find it! + */ + /* LAB5 YOUR CODE */ + //you should update your lab1 code (just add ONE or TWO lines of code), let user app to use syscall to get the service of ucore + //so you should setup the syscall interrupt gate in here +} + +static const char * +trapname(int trapno) { + static const char * const excnames[] = { + "Divide error", + "Debug", + "Non-Maskable Interrupt", + "Breakpoint", + "Overflow", + "BOUND Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack Fault", + "General Protection", + "Page Fault", + "(unknown trap)", + "x87 FPU Floating-Point Error", + "Alignment Check", + "Machine-Check", + "SIMD Floating-Point Exception" + }; + + if (trapno < sizeof(excnames)/sizeof(const char * const)) { + return excnames[trapno]; + } + if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { + return "Hardware Interrupt"; + } + return "(unknown trap)"; +} + +/* trap_in_kernel - test if trap happened in kernel */ +bool +trap_in_kernel(struct trapframe *tf) { + return (tf->tf_cs == (uint16_t)KERNEL_CS); +} + +static const char *IA32flags[] = { + "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", + "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, + "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, +}; + +void +print_trapframe(struct trapframe *tf) { + cprintf("trapframe at %p\n", tf); + print_regs(&tf->tf_regs); + cprintf(" ds 0x----%04x\n", tf->tf_ds); + cprintf(" es 0x----%04x\n", tf->tf_es); + cprintf(" fs 0x----%04x\n", tf->tf_fs); + cprintf(" gs 0x----%04x\n", tf->tf_gs); + cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); + cprintf(" err 0x%08x\n", tf->tf_err); + cprintf(" eip 0x%08x\n", tf->tf_eip); + cprintf(" cs 0x----%04x\n", tf->tf_cs); + cprintf(" flag 0x%08x ", tf->tf_eflags); + + int i, j; + for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { + if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { + cprintf("%s,", IA32flags[i]); + } + } + cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); + + if (!trap_in_kernel(tf)) { + cprintf(" esp 0x%08x\n", tf->tf_esp); + cprintf(" ss 0x----%04x\n", tf->tf_ss); + } +} + +void +print_regs(struct pushregs *regs) { + cprintf(" edi 0x%08x\n", regs->reg_edi); + cprintf(" esi 0x%08x\n", regs->reg_esi); + cprintf(" ebp 0x%08x\n", regs->reg_ebp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" ebx 0x%08x\n", regs->reg_ebx); + cprintf(" edx 0x%08x\n", regs->reg_edx); + cprintf(" ecx 0x%08x\n", regs->reg_ecx); + cprintf(" eax 0x%08x\n", regs->reg_eax); +} + +static inline void +print_pgfault(struct trapframe *tf) { + /* error_code: + * bit 0 == 0 means no page found, 1 means protection fault + * bit 1 == 0 means read, 1 means write + * bit 2 == 0 means kernel, 1 means user + * */ + cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), + (tf->tf_err & 4) ? 'U' : 'K', + (tf->tf_err & 2) ? 'W' : 'R', + (tf->tf_err & 1) ? "protection fault" : "no page found"); +} + +static int +pgfault_handler(struct trapframe *tf) { + extern struct mm_struct *check_mm_struct; + if(check_mm_struct !=NULL) { //used for test check_swap + print_pgfault(tf); + } + struct mm_struct *mm; + if (check_mm_struct != NULL) { + assert(current == idleproc); + mm = check_mm_struct; + } + else { + if (current == NULL) { + print_trapframe(tf); + print_pgfault(tf); + panic("unhandled page fault.\n"); + } + mm = current->mm; + } + return do_pgfault(mm, tf->tf_err, rcr2()); +} + +static volatile int in_swap_tick_event = 0; +extern struct mm_struct *check_mm_struct; + +static void +trap_dispatch(struct trapframe *tf) { + char c; + + int ret=0; + + switch (tf->tf_trapno) { + case T_PGFLT: //page fault + if ((ret = pgfault_handler(tf)) != 0) { + print_trapframe(tf); + if (current == NULL) { + panic("handle pgfault failed. ret=%d\n", ret); + } + else { + if (trap_in_kernel(tf)) { + panic("handle pgfault failed in kernel mode. ret=%d\n", ret); + } + cprintf("killed by kernel.\n"); + panic("handle user mode pgfault failed. ret=%d\n", ret); + do_exit(-E_KILLED); + } + } + break; + case T_SYSCALL: + syscall(); + break; + case IRQ_OFFSET + IRQ_TIMER: +#if 0 + LAB3 : If some page replacement algorithm need tick to change the priority of pages, + then you can add code here. +#endif + /* LAB1 YOUR CODE : STEP 3 */ + /* handle the timer interrupt */ + /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c + * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). + * (3) Too Simple? Yes, I think so! + */ + /* LAB5 YOUR CODE */ + /* you should upate you lab1 code (just add ONE or TWO lines of code): + * Every TICK_NUM cycle, you should set current process's current->need_resched = 1 + */ + + break; + case IRQ_OFFSET + IRQ_COM1: + c = cons_getc(); + cprintf("serial [%03d] %c\n", c, c); + break; + case IRQ_OFFSET + IRQ_KBD: + c = cons_getc(); + cprintf("kbd [%03d] %c\n", c, c); + break; + //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. + case T_SWITCH_TOU: + case T_SWITCH_TOK: + panic("T_SWITCH_** ??\n"); + break; + case IRQ_OFFSET + IRQ_IDE1: + case IRQ_OFFSET + IRQ_IDE2: + /* do nothing */ + break; + default: + print_trapframe(tf); + if (current != NULL) { + cprintf("unhandled trap.\n"); + do_exit(-E_KILLED); + } + // in kernel, it must be a mistake + panic("unexpected trap in kernel.\n"); + + } +} + +/* * + * trap - handles or dispatches an exception/interrupt. if and when trap() returns, + * the code in kern/trap/trapentry.S restores the old CPU state saved in the + * trapframe and then uses the iret instruction to return from the exception. + * */ +void +trap(struct trapframe *tf) { + // dispatch based on what type of trap occurred + // used for previous projects + if (current == NULL) { + trap_dispatch(tf); + } + else { + // keep a trapframe chain in stack + struct trapframe *otf = current->tf; + current->tf = tf; + + bool in_kernel = trap_in_kernel(tf); + + trap_dispatch(tf); + + current->tf = otf; + if (!in_kernel) { + if (current->flags & PF_EXITING) { + do_exit(-E_KILLED); + } + if (current->need_resched) { + schedule(); + } + } + } +} + diff --git a/code/lab8/kern/trap/trap.h b/code/lab8/kern/trap/trap.h new file mode 100644 index 0000000..e870a6f --- /dev/null +++ b/code/lab8/kern/trap/trap.h @@ -0,0 +1,89 @@ +#ifndef __KERN_TRAP_TRAP_H__ +#define __KERN_TRAP_TRAP_H__ + +#include + +/* Trap Numbers */ + +/* Processor-defined: */ +#define T_DIVIDE 0 // divide error +#define T_DEBUG 1 // debug exception +#define T_NMI 2 // non-maskable interrupt +#define T_BRKPT 3 // breakpoint +#define T_OFLOW 4 // overflow +#define T_BOUND 5 // bounds check +#define T_ILLOP 6 // illegal opcode +#define T_DEVICE 7 // device not available +#define T_DBLFLT 8 // double fault +// #define T_COPROC 9 // reserved (not used since 486) +#define T_TSS 10 // invalid task switch segment +#define T_SEGNP 11 // segment not present +#define T_STACK 12 // stack exception +#define T_GPFLT 13 // general protection fault +#define T_PGFLT 14 // page fault +// #define T_RES 15 // reserved +#define T_FPERR 16 // floating point error +#define T_ALIGN 17 // aligment check +#define T_MCHK 18 // machine check +#define T_SIMDERR 19 // SIMD floating point error + +/* Hardware IRQ numbers. We receive these as (IRQ_OFFSET + IRQ_xx) */ +#define IRQ_OFFSET 32 // IRQ 0 corresponds to int IRQ_OFFSET + +#define IRQ_TIMER 0 +#define IRQ_KBD 1 +#define IRQ_COM1 4 +#define IRQ_IDE1 14 +#define IRQ_IDE2 15 +#define IRQ_ERROR 19 +#define IRQ_SPURIOUS 31 + +/* * + * These are arbitrarily chosen, but with care not to overlap + * processor defined exceptions or interrupt vectors. + * */ +#define T_SWITCH_TOU 120 // user/kernel switch +#define T_SWITCH_TOK 121 // user/kernel switch + +/* registers as pushed by pushal */ +struct pushregs { + uint32_t reg_edi; + uint32_t reg_esi; + uint32_t reg_ebp; + uint32_t reg_oesp; /* Useless */ + uint32_t reg_ebx; + uint32_t reg_edx; + uint32_t reg_ecx; + uint32_t reg_eax; +}; + +struct trapframe { + struct pushregs tf_regs; + uint16_t tf_gs; + uint16_t tf_padding0; + uint16_t tf_fs; + uint16_t tf_padding1; + uint16_t tf_es; + uint16_t tf_padding2; + uint16_t tf_ds; + uint16_t tf_padding3; + uint32_t tf_trapno; + /* below here defined by x86 hardware */ + uint32_t tf_err; + uintptr_t tf_eip; + uint16_t tf_cs; + uint16_t tf_padding4; + uint32_t tf_eflags; + /* below here only when crossing rings, such as from user to kernel */ + uintptr_t tf_esp; + uint16_t tf_ss; + uint16_t tf_padding5; +} __attribute__((packed)); + +void idt_init(void); +void print_trapframe(struct trapframe *tf); +void print_regs(struct pushregs *regs); +bool trap_in_kernel(struct trapframe *tf); + +#endif /* !__KERN_TRAP_TRAP_H__ */ + diff --git a/code/lab8/kern/trap/trapentry.S b/code/lab8/kern/trap/trapentry.S new file mode 100644 index 0000000..3565ec8 --- /dev/null +++ b/code/lab8/kern/trap/trapentry.S @@ -0,0 +1,49 @@ +#include + +# vectors.S sends all traps here. +.text +.globl __alltraps +__alltraps: + # push registers to build a trap frame + # therefore make the stack look like a struct trapframe + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + # load GD_KDATA into %ds and %es to set up data segments for kernel + movl $GD_KDATA, %eax + movw %ax, %ds + movw %ax, %es + + # push %esp to pass a pointer to the trapframe as an argument to trap() + pushl %esp + + # call trap(tf), where tf=%esp + call trap + + # pop the pushed stack pointer + popl %esp + + # return falls through to trapret... +.globl __trapret +__trapret: + # restore registers from stack + popal + + # restore %ds, %es, %fs and %gs + popl %gs + popl %fs + popl %es + popl %ds + + # get rid of the trap number and error code + addl $0x8, %esp + iret + +.globl forkrets +forkrets: + # set stack to this new process's trapframe + movl 4(%esp), %esp + jmp __trapret diff --git a/code/lab8/kern/trap/vectors.S b/code/lab8/kern/trap/vectors.S new file mode 100644 index 0000000..1d05b4a --- /dev/null +++ b/code/lab8/kern/trap/vectors.S @@ -0,0 +1,1536 @@ +# handler +.text +.globl __alltraps +.globl vector0 +vector0: + pushl $0 + pushl $0 + jmp __alltraps +.globl vector1 +vector1: + pushl $0 + pushl $1 + jmp __alltraps +.globl vector2 +vector2: + pushl $0 + pushl $2 + jmp __alltraps +.globl vector3 +vector3: + pushl $0 + pushl $3 + jmp __alltraps +.globl vector4 +vector4: + pushl $0 + pushl $4 + jmp __alltraps +.globl vector5 +vector5: + pushl $0 + pushl $5 + jmp __alltraps +.globl vector6 +vector6: + pushl $0 + pushl $6 + jmp __alltraps +.globl vector7 +vector7: + pushl $0 + pushl $7 + jmp __alltraps +.globl vector8 +vector8: + pushl $8 + jmp __alltraps +.globl vector9 +vector9: + pushl $9 + jmp __alltraps +.globl vector10 +vector10: + pushl $10 + jmp __alltraps +.globl vector11 +vector11: + pushl $11 + jmp __alltraps +.globl vector12 +vector12: + pushl $12 + jmp __alltraps +.globl vector13 +vector13: + pushl $13 + jmp __alltraps +.globl vector14 +vector14: + pushl $14 + jmp __alltraps +.globl vector15 +vector15: + pushl $0 + pushl $15 + jmp __alltraps +.globl vector16 +vector16: + pushl $0 + pushl $16 + jmp __alltraps +.globl vector17 +vector17: + pushl $17 + jmp __alltraps +.globl vector18 +vector18: + pushl $0 + pushl $18 + jmp __alltraps +.globl vector19 +vector19: + pushl $0 + pushl $19 + jmp __alltraps +.globl vector20 +vector20: + pushl $0 + pushl $20 + jmp __alltraps +.globl vector21 +vector21: + pushl $0 + pushl $21 + jmp __alltraps +.globl vector22 +vector22: + pushl $0 + pushl $22 + jmp __alltraps +.globl vector23 +vector23: + pushl $0 + pushl $23 + jmp __alltraps +.globl vector24 +vector24: + pushl $0 + pushl $24 + jmp __alltraps +.globl vector25 +vector25: + pushl $0 + pushl $25 + jmp __alltraps +.globl vector26 +vector26: + pushl $0 + pushl $26 + jmp __alltraps +.globl vector27 +vector27: + pushl $0 + pushl $27 + jmp __alltraps +.globl vector28 +vector28: + pushl $0 + pushl $28 + jmp __alltraps +.globl vector29 +vector29: + pushl $0 + pushl $29 + jmp __alltraps +.globl vector30 +vector30: + pushl $0 + pushl $30 + jmp __alltraps +.globl vector31 +vector31: + pushl $0 + pushl $31 + jmp __alltraps +.globl vector32 +vector32: + pushl $0 + pushl $32 + jmp __alltraps +.globl vector33 +vector33: + pushl $0 + pushl $33 + jmp __alltraps +.globl vector34 +vector34: + pushl $0 + pushl $34 + jmp __alltraps +.globl vector35 +vector35: + pushl $0 + pushl $35 + jmp __alltraps +.globl vector36 +vector36: + pushl $0 + pushl $36 + jmp __alltraps +.globl vector37 +vector37: + pushl $0 + pushl $37 + jmp __alltraps +.globl vector38 +vector38: + pushl $0 + pushl $38 + jmp __alltraps +.globl vector39 +vector39: + pushl $0 + pushl $39 + jmp __alltraps +.globl vector40 +vector40: + pushl $0 + pushl $40 + jmp __alltraps +.globl vector41 +vector41: + pushl $0 + pushl $41 + jmp __alltraps +.globl vector42 +vector42: + pushl $0 + pushl $42 + jmp __alltraps +.globl vector43 +vector43: + pushl $0 + pushl $43 + jmp __alltraps +.globl vector44 +vector44: + pushl $0 + pushl $44 + jmp __alltraps +.globl vector45 +vector45: + pushl $0 + pushl $45 + jmp __alltraps +.globl vector46 +vector46: + pushl $0 + pushl $46 + jmp __alltraps +.globl vector47 +vector47: + pushl $0 + pushl $47 + jmp __alltraps +.globl vector48 +vector48: + pushl $0 + pushl $48 + jmp __alltraps +.globl vector49 +vector49: + pushl $0 + pushl $49 + jmp __alltraps +.globl vector50 +vector50: + pushl $0 + pushl $50 + jmp __alltraps +.globl vector51 +vector51: + pushl $0 + pushl $51 + jmp __alltraps +.globl vector52 +vector52: + pushl $0 + pushl $52 + jmp __alltraps +.globl vector53 +vector53: + pushl $0 + pushl $53 + jmp __alltraps +.globl vector54 +vector54: + pushl $0 + pushl $54 + jmp __alltraps +.globl vector55 +vector55: + pushl $0 + pushl $55 + jmp __alltraps +.globl vector56 +vector56: + pushl $0 + pushl $56 + jmp __alltraps +.globl vector57 +vector57: + pushl $0 + pushl $57 + jmp __alltraps +.globl vector58 +vector58: + pushl $0 + pushl $58 + jmp __alltraps +.globl vector59 +vector59: + pushl $0 + pushl $59 + jmp __alltraps +.globl vector60 +vector60: + pushl $0 + pushl $60 + jmp __alltraps +.globl vector61 +vector61: + pushl $0 + pushl $61 + jmp __alltraps +.globl vector62 +vector62: + pushl $0 + pushl $62 + jmp __alltraps +.globl vector63 +vector63: + pushl $0 + pushl $63 + jmp __alltraps +.globl vector64 +vector64: + pushl $0 + pushl $64 + jmp __alltraps +.globl vector65 +vector65: + pushl $0 + pushl $65 + jmp __alltraps +.globl vector66 +vector66: + pushl $0 + pushl $66 + jmp __alltraps +.globl vector67 +vector67: + pushl $0 + pushl $67 + jmp __alltraps +.globl vector68 +vector68: + pushl $0 + pushl $68 + jmp __alltraps +.globl vector69 +vector69: + pushl $0 + pushl $69 + jmp __alltraps +.globl vector70 +vector70: + pushl $0 + pushl $70 + jmp __alltraps +.globl vector71 +vector71: + pushl $0 + pushl $71 + jmp __alltraps +.globl vector72 +vector72: + pushl $0 + pushl $72 + jmp __alltraps +.globl vector73 +vector73: + pushl $0 + pushl $73 + jmp __alltraps +.globl vector74 +vector74: + pushl $0 + pushl $74 + jmp __alltraps +.globl vector75 +vector75: + pushl $0 + pushl $75 + jmp __alltraps +.globl vector76 +vector76: + pushl $0 + pushl $76 + jmp __alltraps +.globl vector77 +vector77: + pushl $0 + pushl $77 + jmp __alltraps +.globl vector78 +vector78: + pushl $0 + pushl $78 + jmp __alltraps +.globl vector79 +vector79: + pushl $0 + pushl $79 + jmp __alltraps +.globl vector80 +vector80: + pushl $0 + pushl $80 + jmp __alltraps +.globl vector81 +vector81: + pushl $0 + pushl $81 + jmp __alltraps +.globl vector82 +vector82: + pushl $0 + pushl $82 + jmp __alltraps +.globl vector83 +vector83: + pushl $0 + pushl $83 + jmp __alltraps +.globl vector84 +vector84: + pushl $0 + pushl $84 + jmp __alltraps +.globl vector85 +vector85: + pushl $0 + pushl $85 + jmp __alltraps +.globl vector86 +vector86: + pushl $0 + pushl $86 + jmp __alltraps +.globl vector87 +vector87: + pushl $0 + pushl $87 + jmp __alltraps +.globl vector88 +vector88: + pushl $0 + pushl $88 + jmp __alltraps +.globl vector89 +vector89: + pushl $0 + pushl $89 + jmp __alltraps +.globl vector90 +vector90: + pushl $0 + pushl $90 + jmp __alltraps +.globl vector91 +vector91: + pushl $0 + pushl $91 + jmp __alltraps +.globl vector92 +vector92: + pushl $0 + pushl $92 + jmp __alltraps +.globl vector93 +vector93: + pushl $0 + pushl $93 + jmp __alltraps +.globl vector94 +vector94: + pushl $0 + pushl $94 + jmp __alltraps +.globl vector95 +vector95: + pushl $0 + pushl $95 + jmp __alltraps +.globl vector96 +vector96: + pushl $0 + pushl $96 + jmp __alltraps +.globl vector97 +vector97: + pushl $0 + pushl $97 + jmp __alltraps +.globl vector98 +vector98: + pushl $0 + pushl $98 + jmp __alltraps +.globl vector99 +vector99: + pushl $0 + pushl $99 + jmp __alltraps +.globl vector100 +vector100: + pushl $0 + pushl $100 + jmp __alltraps +.globl vector101 +vector101: + pushl $0 + pushl $101 + jmp __alltraps +.globl vector102 +vector102: + pushl $0 + pushl $102 + jmp __alltraps +.globl vector103 +vector103: + pushl $0 + pushl $103 + jmp __alltraps +.globl vector104 +vector104: + pushl $0 + pushl $104 + jmp __alltraps +.globl vector105 +vector105: + pushl $0 + pushl $105 + jmp __alltraps +.globl vector106 +vector106: + pushl $0 + pushl $106 + jmp __alltraps +.globl vector107 +vector107: + pushl $0 + pushl $107 + jmp __alltraps +.globl vector108 +vector108: + pushl $0 + pushl $108 + jmp __alltraps +.globl vector109 +vector109: + pushl $0 + pushl $109 + jmp __alltraps +.globl vector110 +vector110: + pushl $0 + pushl $110 + jmp __alltraps +.globl vector111 +vector111: + pushl $0 + pushl $111 + jmp __alltraps +.globl vector112 +vector112: + pushl $0 + pushl $112 + jmp __alltraps +.globl vector113 +vector113: + pushl $0 + pushl $113 + jmp __alltraps +.globl vector114 +vector114: + pushl $0 + pushl $114 + jmp __alltraps +.globl vector115 +vector115: + pushl $0 + pushl $115 + jmp __alltraps +.globl vector116 +vector116: + pushl $0 + pushl $116 + jmp __alltraps +.globl vector117 +vector117: + pushl $0 + pushl $117 + jmp __alltraps +.globl vector118 +vector118: + pushl $0 + pushl $118 + jmp __alltraps +.globl vector119 +vector119: + pushl $0 + pushl $119 + jmp __alltraps +.globl vector120 +vector120: + pushl $0 + pushl $120 + jmp __alltraps +.globl vector121 +vector121: + pushl $0 + pushl $121 + jmp __alltraps +.globl vector122 +vector122: + pushl $0 + pushl $122 + jmp __alltraps +.globl vector123 +vector123: + pushl $0 + pushl $123 + jmp __alltraps +.globl vector124 +vector124: + pushl $0 + pushl $124 + jmp __alltraps +.globl vector125 +vector125: + pushl $0 + pushl $125 + jmp __alltraps +.globl vector126 +vector126: + pushl $0 + pushl $126 + jmp __alltraps +.globl vector127 +vector127: + pushl $0 + pushl $127 + jmp __alltraps +.globl vector128 +vector128: + pushl $0 + pushl $128 + jmp __alltraps +.globl vector129 +vector129: + pushl $0 + pushl $129 + jmp __alltraps +.globl vector130 +vector130: + pushl $0 + pushl $130 + jmp __alltraps +.globl vector131 +vector131: + pushl $0 + pushl $131 + jmp __alltraps +.globl vector132 +vector132: + pushl $0 + pushl $132 + jmp __alltraps +.globl vector133 +vector133: + pushl $0 + pushl $133 + jmp __alltraps +.globl vector134 +vector134: + pushl $0 + pushl $134 + jmp __alltraps +.globl vector135 +vector135: + pushl $0 + pushl $135 + jmp __alltraps +.globl vector136 +vector136: + pushl $0 + pushl $136 + jmp __alltraps +.globl vector137 +vector137: + pushl $0 + pushl $137 + jmp __alltraps +.globl vector138 +vector138: + pushl $0 + pushl $138 + jmp __alltraps +.globl vector139 +vector139: + pushl $0 + pushl $139 + jmp __alltraps +.globl vector140 +vector140: + pushl $0 + pushl $140 + jmp __alltraps +.globl vector141 +vector141: + pushl $0 + pushl $141 + jmp __alltraps +.globl vector142 +vector142: + pushl $0 + pushl $142 + jmp __alltraps +.globl vector143 +vector143: + pushl $0 + pushl $143 + jmp __alltraps +.globl vector144 +vector144: + pushl $0 + pushl $144 + jmp __alltraps +.globl vector145 +vector145: + pushl $0 + pushl $145 + jmp __alltraps +.globl vector146 +vector146: + pushl $0 + pushl $146 + jmp __alltraps +.globl vector147 +vector147: + pushl $0 + pushl $147 + jmp __alltraps +.globl vector148 +vector148: + pushl $0 + pushl $148 + jmp __alltraps +.globl vector149 +vector149: + pushl $0 + pushl $149 + jmp __alltraps +.globl vector150 +vector150: + pushl $0 + pushl $150 + jmp __alltraps +.globl vector151 +vector151: + pushl $0 + pushl $151 + jmp __alltraps +.globl vector152 +vector152: + pushl $0 + pushl $152 + jmp __alltraps +.globl vector153 +vector153: + pushl $0 + pushl $153 + jmp __alltraps +.globl vector154 +vector154: + pushl $0 + pushl $154 + jmp __alltraps +.globl vector155 +vector155: + pushl $0 + pushl $155 + jmp __alltraps +.globl vector156 +vector156: + pushl $0 + pushl $156 + jmp __alltraps +.globl vector157 +vector157: + pushl $0 + pushl $157 + jmp __alltraps +.globl vector158 +vector158: + pushl $0 + pushl $158 + jmp __alltraps +.globl vector159 +vector159: + pushl $0 + pushl $159 + jmp __alltraps +.globl vector160 +vector160: + pushl $0 + pushl $160 + jmp __alltraps +.globl vector161 +vector161: + pushl $0 + pushl $161 + jmp __alltraps +.globl vector162 +vector162: + pushl $0 + pushl $162 + jmp __alltraps +.globl vector163 +vector163: + pushl $0 + pushl $163 + jmp __alltraps +.globl vector164 +vector164: + pushl $0 + pushl $164 + jmp __alltraps +.globl vector165 +vector165: + pushl $0 + pushl $165 + jmp __alltraps +.globl vector166 +vector166: + pushl $0 + pushl $166 + jmp __alltraps +.globl vector167 +vector167: + pushl $0 + pushl $167 + jmp __alltraps +.globl vector168 +vector168: + pushl $0 + pushl $168 + jmp __alltraps +.globl vector169 +vector169: + pushl $0 + pushl $169 + jmp __alltraps +.globl vector170 +vector170: + pushl $0 + pushl $170 + jmp __alltraps +.globl vector171 +vector171: + pushl $0 + pushl $171 + jmp __alltraps +.globl vector172 +vector172: + pushl $0 + pushl $172 + jmp __alltraps +.globl vector173 +vector173: + pushl $0 + pushl $173 + jmp __alltraps +.globl vector174 +vector174: + pushl $0 + pushl $174 + jmp __alltraps +.globl vector175 +vector175: + pushl $0 + pushl $175 + jmp __alltraps +.globl vector176 +vector176: + pushl $0 + pushl $176 + jmp __alltraps +.globl vector177 +vector177: + pushl $0 + pushl $177 + jmp __alltraps +.globl vector178 +vector178: + pushl $0 + pushl $178 + jmp __alltraps +.globl vector179 +vector179: + pushl $0 + pushl $179 + jmp __alltraps +.globl vector180 +vector180: + pushl $0 + pushl $180 + jmp __alltraps +.globl vector181 +vector181: + pushl $0 + pushl $181 + jmp __alltraps +.globl vector182 +vector182: + pushl $0 + pushl $182 + jmp __alltraps +.globl vector183 +vector183: + pushl $0 + pushl $183 + jmp __alltraps +.globl vector184 +vector184: + pushl $0 + pushl $184 + jmp __alltraps +.globl vector185 +vector185: + pushl $0 + pushl $185 + jmp __alltraps +.globl vector186 +vector186: + pushl $0 + pushl $186 + jmp __alltraps +.globl vector187 +vector187: + pushl $0 + pushl $187 + jmp __alltraps +.globl vector188 +vector188: + pushl $0 + pushl $188 + jmp __alltraps +.globl vector189 +vector189: + pushl $0 + pushl $189 + jmp __alltraps +.globl vector190 +vector190: + pushl $0 + pushl $190 + jmp __alltraps +.globl vector191 +vector191: + pushl $0 + pushl $191 + jmp __alltraps +.globl vector192 +vector192: + pushl $0 + pushl $192 + jmp __alltraps +.globl vector193 +vector193: + pushl $0 + pushl $193 + jmp __alltraps +.globl vector194 +vector194: + pushl $0 + pushl $194 + jmp __alltraps +.globl vector195 +vector195: + pushl $0 + pushl $195 + jmp __alltraps +.globl vector196 +vector196: + pushl $0 + pushl $196 + jmp __alltraps +.globl vector197 +vector197: + pushl $0 + pushl $197 + jmp __alltraps +.globl vector198 +vector198: + pushl $0 + pushl $198 + jmp __alltraps +.globl vector199 +vector199: + pushl $0 + pushl $199 + jmp __alltraps +.globl vector200 +vector200: + pushl $0 + pushl $200 + jmp __alltraps +.globl vector201 +vector201: + pushl $0 + pushl $201 + jmp __alltraps +.globl vector202 +vector202: + pushl $0 + pushl $202 + jmp __alltraps +.globl vector203 +vector203: + pushl $0 + pushl $203 + jmp __alltraps +.globl vector204 +vector204: + pushl $0 + pushl $204 + jmp __alltraps +.globl vector205 +vector205: + pushl $0 + pushl $205 + jmp __alltraps +.globl vector206 +vector206: + pushl $0 + pushl $206 + jmp __alltraps +.globl vector207 +vector207: + pushl $0 + pushl $207 + jmp __alltraps +.globl vector208 +vector208: + pushl $0 + pushl $208 + jmp __alltraps +.globl vector209 +vector209: + pushl $0 + pushl $209 + jmp __alltraps +.globl vector210 +vector210: + pushl $0 + pushl $210 + jmp __alltraps +.globl vector211 +vector211: + pushl $0 + pushl $211 + jmp __alltraps +.globl vector212 +vector212: + pushl $0 + pushl $212 + jmp __alltraps +.globl vector213 +vector213: + pushl $0 + pushl $213 + jmp __alltraps +.globl vector214 +vector214: + pushl $0 + pushl $214 + jmp __alltraps +.globl vector215 +vector215: + pushl $0 + pushl $215 + jmp __alltraps +.globl vector216 +vector216: + pushl $0 + pushl $216 + jmp __alltraps +.globl vector217 +vector217: + pushl $0 + pushl $217 + jmp __alltraps +.globl vector218 +vector218: + pushl $0 + pushl $218 + jmp __alltraps +.globl vector219 +vector219: + pushl $0 + pushl $219 + jmp __alltraps +.globl vector220 +vector220: + pushl $0 + pushl $220 + jmp __alltraps +.globl vector221 +vector221: + pushl $0 + pushl $221 + jmp __alltraps +.globl vector222 +vector222: + pushl $0 + pushl $222 + jmp __alltraps +.globl vector223 +vector223: + pushl $0 + pushl $223 + jmp __alltraps +.globl vector224 +vector224: + pushl $0 + pushl $224 + jmp __alltraps +.globl vector225 +vector225: + pushl $0 + pushl $225 + jmp __alltraps +.globl vector226 +vector226: + pushl $0 + pushl $226 + jmp __alltraps +.globl vector227 +vector227: + pushl $0 + pushl $227 + jmp __alltraps +.globl vector228 +vector228: + pushl $0 + pushl $228 + jmp __alltraps +.globl vector229 +vector229: + pushl $0 + pushl $229 + jmp __alltraps +.globl vector230 +vector230: + pushl $0 + pushl $230 + jmp __alltraps +.globl vector231 +vector231: + pushl $0 + pushl $231 + jmp __alltraps +.globl vector232 +vector232: + pushl $0 + pushl $232 + jmp __alltraps +.globl vector233 +vector233: + pushl $0 + pushl $233 + jmp __alltraps +.globl vector234 +vector234: + pushl $0 + pushl $234 + jmp __alltraps +.globl vector235 +vector235: + pushl $0 + pushl $235 + jmp __alltraps +.globl vector236 +vector236: + pushl $0 + pushl $236 + jmp __alltraps +.globl vector237 +vector237: + pushl $0 + pushl $237 + jmp __alltraps +.globl vector238 +vector238: + pushl $0 + pushl $238 + jmp __alltraps +.globl vector239 +vector239: + pushl $0 + pushl $239 + jmp __alltraps +.globl vector240 +vector240: + pushl $0 + pushl $240 + jmp __alltraps +.globl vector241 +vector241: + pushl $0 + pushl $241 + jmp __alltraps +.globl vector242 +vector242: + pushl $0 + pushl $242 + jmp __alltraps +.globl vector243 +vector243: + pushl $0 + pushl $243 + jmp __alltraps +.globl vector244 +vector244: + pushl $0 + pushl $244 + jmp __alltraps +.globl vector245 +vector245: + pushl $0 + pushl $245 + jmp __alltraps +.globl vector246 +vector246: + pushl $0 + pushl $246 + jmp __alltraps +.globl vector247 +vector247: + pushl $0 + pushl $247 + jmp __alltraps +.globl vector248 +vector248: + pushl $0 + pushl $248 + jmp __alltraps +.globl vector249 +vector249: + pushl $0 + pushl $249 + jmp __alltraps +.globl vector250 +vector250: + pushl $0 + pushl $250 + jmp __alltraps +.globl vector251 +vector251: + pushl $0 + pushl $251 + jmp __alltraps +.globl vector252 +vector252: + pushl $0 + pushl $252 + jmp __alltraps +.globl vector253 +vector253: + pushl $0 + pushl $253 + jmp __alltraps +.globl vector254 +vector254: + pushl $0 + pushl $254 + jmp __alltraps +.globl vector255 +vector255: + pushl $0 + pushl $255 + jmp __alltraps + +# vector table +.data +.globl __vectors +__vectors: + .long vector0 + .long vector1 + .long vector2 + .long vector3 + .long vector4 + .long vector5 + .long vector6 + .long vector7 + .long vector8 + .long vector9 + .long vector10 + .long vector11 + .long vector12 + .long vector13 + .long vector14 + .long vector15 + .long vector16 + .long vector17 + .long vector18 + .long vector19 + .long vector20 + .long vector21 + .long vector22 + .long vector23 + .long vector24 + .long vector25 + .long vector26 + .long vector27 + .long vector28 + .long vector29 + .long vector30 + .long vector31 + .long vector32 + .long vector33 + .long vector34 + .long vector35 + .long vector36 + .long vector37 + .long vector38 + .long vector39 + .long vector40 + .long vector41 + .long vector42 + .long vector43 + .long vector44 + .long vector45 + .long vector46 + .long vector47 + .long vector48 + .long vector49 + .long vector50 + .long vector51 + .long vector52 + .long vector53 + .long vector54 + .long vector55 + .long vector56 + .long vector57 + .long vector58 + .long vector59 + .long vector60 + .long vector61 + .long vector62 + .long vector63 + .long vector64 + .long vector65 + .long vector66 + .long vector67 + .long vector68 + .long vector69 + .long vector70 + .long vector71 + .long vector72 + .long vector73 + .long vector74 + .long vector75 + .long vector76 + .long vector77 + .long vector78 + .long vector79 + .long vector80 + .long vector81 + .long vector82 + .long vector83 + .long vector84 + .long vector85 + .long vector86 + .long vector87 + .long vector88 + .long vector89 + .long vector90 + .long vector91 + .long vector92 + .long vector93 + .long vector94 + .long vector95 + .long vector96 + .long vector97 + .long vector98 + .long vector99 + .long vector100 + .long vector101 + .long vector102 + .long vector103 + .long vector104 + .long vector105 + .long vector106 + .long vector107 + .long vector108 + .long vector109 + .long vector110 + .long vector111 + .long vector112 + .long vector113 + .long vector114 + .long vector115 + .long vector116 + .long vector117 + .long vector118 + .long vector119 + .long vector120 + .long vector121 + .long vector122 + .long vector123 + .long vector124 + .long vector125 + .long vector126 + .long vector127 + .long vector128 + .long vector129 + .long vector130 + .long vector131 + .long vector132 + .long vector133 + .long vector134 + .long vector135 + .long vector136 + .long vector137 + .long vector138 + .long vector139 + .long vector140 + .long vector141 + .long vector142 + .long vector143 + .long vector144 + .long vector145 + .long vector146 + .long vector147 + .long vector148 + .long vector149 + .long vector150 + .long vector151 + .long vector152 + .long vector153 + .long vector154 + .long vector155 + .long vector156 + .long vector157 + .long vector158 + .long vector159 + .long vector160 + .long vector161 + .long vector162 + .long vector163 + .long vector164 + .long vector165 + .long vector166 + .long vector167 + .long vector168 + .long vector169 + .long vector170 + .long vector171 + .long vector172 + .long vector173 + .long vector174 + .long vector175 + .long vector176 + .long vector177 + .long vector178 + .long vector179 + .long vector180 + .long vector181 + .long vector182 + .long vector183 + .long vector184 + .long vector185 + .long vector186 + .long vector187 + .long vector188 + .long vector189 + .long vector190 + .long vector191 + .long vector192 + .long vector193 + .long vector194 + .long vector195 + .long vector196 + .long vector197 + .long vector198 + .long vector199 + .long vector200 + .long vector201 + .long vector202 + .long vector203 + .long vector204 + .long vector205 + .long vector206 + .long vector207 + .long vector208 + .long vector209 + .long vector210 + .long vector211 + .long vector212 + .long vector213 + .long vector214 + .long vector215 + .long vector216 + .long vector217 + .long vector218 + .long vector219 + .long vector220 + .long vector221 + .long vector222 + .long vector223 + .long vector224 + .long vector225 + .long vector226 + .long vector227 + .long vector228 + .long vector229 + .long vector230 + .long vector231 + .long vector232 + .long vector233 + .long vector234 + .long vector235 + .long vector236 + .long vector237 + .long vector238 + .long vector239 + .long vector240 + .long vector241 + .long vector242 + .long vector243 + .long vector244 + .long vector245 + .long vector246 + .long vector247 + .long vector248 + .long vector249 + .long vector250 + .long vector251 + .long vector252 + .long vector253 + .long vector254 + .long vector255 diff --git a/code/lab8/libs/atomic.h b/code/lab8/libs/atomic.h new file mode 100644 index 0000000..a3a9525 --- /dev/null +++ b/code/lab8/libs/atomic.h @@ -0,0 +1,251 @@ +#ifndef __LIBS_ATOMIC_H__ +#define __LIBS_ATOMIC_H__ + +/* Atomic operations that C can't guarantee us. Useful for resource counting etc.. */ + +typedef struct { + volatile int counter; +} atomic_t; + +static inline int atomic_read(const atomic_t *v) __attribute__((always_inline)); +static inline void atomic_set(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_add(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_sub(atomic_t *v, int i) __attribute__((always_inline)); +static inline bool atomic_sub_test_zero(atomic_t *v, int i) __attribute__((always_inline)); +static inline void atomic_inc(atomic_t *v) __attribute__((always_inline)); +static inline void atomic_dec(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_inc_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline bool atomic_dec_test_zero(atomic_t *v) __attribute__((always_inline)); +static inline int atomic_add_return(atomic_t *v, int i) __attribute__((always_inline)); +static inline int atomic_sub_return(atomic_t *v, int i) __attribute__((always_inline)); + +/* * + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + * */ +static inline int +atomic_read(const atomic_t *v) { + return v->counter; +} + +/* * + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + * */ +static inline void +atomic_set(atomic_t *v, int i) { + v->counter = i; +} + +/* * + * atomic_add - add integer to atomic variable + * @v: pointer of type atomic_t + * @i: integer value to add + * + * Atomically adds @i to @v. + * */ +static inline void +atomic_add(atomic_t *v, int i) { + asm volatile ("addl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub - subtract integer from atomic variable + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v. + * */ +static inline void +atomic_sub(atomic_t *v, int i) { + asm volatile("subl %1, %0" : "+m" (v->counter) : "ir" (i)); +} + +/* * + * atomic_sub_test_zero - subtract value from variable and test result + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_sub_test_zero(atomic_t *v, int i) { + unsigned char c; + asm volatile("subl %2, %0; sete %1" : "+m" (v->counter), "=qm" (c) : "ir" (i) : "memory"); + return c != 0; +} + +/* * + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + * */ +static inline void +atomic_inc(atomic_t *v) { + asm volatile("incl %0" : "+m" (v->counter)); +} + +/* * + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + * */ +static inline void +atomic_dec(atomic_t *v) { + asm volatile("decl %0" : "+m" (v->counter)); +} + +/* * + * atomic_inc_test_zero - increment and test + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1 and + * returns true if the result is zero, or false for all other cases. + * */ +static inline bool +atomic_inc_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("incl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_dec_test_zero - decrement and test + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1 and + * returns true if the result is 0, or false for all other cases. + * */ +static inline bool +atomic_dec_test_zero(atomic_t *v) { + unsigned char c; + asm volatile("decl %0; sete %1" : "+m" (v->counter), "=qm" (c) :: "memory"); + return c != 0; +} + +/* * + * atomic_add_return - add integer and return + * @i: integer value to add + * @v: pointer of type atomic_t + * + * Atomically adds @i to @v and returns @i + @v + * Requires Modern 486+ processor + * */ +static inline int +atomic_add_return(atomic_t *v, int i) { + int __i = i; + asm volatile("xaddl %0, %1" : "+r" (i), "+m" (v->counter) :: "memory"); + return i + __i; +} + +/* * + * atomic_sub_return - subtract integer and return + * @v: pointer of type atomic_t + * @i: integer value to subtract + * + * Atomically subtracts @i from @v and returns @v - @i + * */ +static inline int +atomic_sub_return(atomic_t *v, int i) { + return atomic_add_return(v, -i); +} + +static inline void set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline void change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_set_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_clear_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_and_change_bit(int nr, volatile void *addr) __attribute__((always_inline)); +static inline bool test_bit(int nr, volatile void *addr) __attribute__((always_inline)); + +/* * + * set_bit - Atomically set a bit in memory + * @nr: the bit to set + * @addr: the address to start counting from + * + * Note that @nr may be almost arbitrarily large; this function is not + * restricted to acting on a single-word quantity. + * */ +static inline void +set_bit(int nr, volatile void *addr) { + asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * clear_bit - Atomically clears a bit in memory + * @nr: the bit to clear + * @addr: the address to start counting from + * */ +static inline void +clear_bit(int nr, volatile void *addr) { + asm volatile ("btrl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * change_bit - Atomically toggle a bit in memory + * @nr: the bit to change + * @addr: the address to start counting from + * */ +static inline void +change_bit(int nr, volatile void *addr) { + asm volatile ("btcl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr)); +} + +/* * + * test_and_set_bit - Atomically set a bit and return its old value + * @nr: the bit to set + * @addr: the address to count from + * */ +static inline bool +test_and_set_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btsl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_clear_bit - Atomically clear a bit and return its old value + * @nr: the bit to clear + * @addr: the address to count from + * */ +static inline bool +test_and_clear_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btrl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_and_change_bit - Atomically change a bit and return its old value + * @nr: the bit to change + * @addr: the address to count from + * */ +static inline bool +test_and_change_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btcl %2, %1; sbbl %0, %0" : "=r" (oldbit), "=m" (*(volatile long *)addr) : "Ir" (nr) : "memory"); + return oldbit != 0; +} + +/* * + * test_bit - Determine whether a bit is set + * @nr: the bit to test + * @addr: the address to count from + * */ +static inline bool +test_bit(int nr, volatile void *addr) { + int oldbit; + asm volatile ("btl %2, %1; sbbl %0,%0" : "=r" (oldbit) : "m" (*(volatile long *)addr), "Ir" (nr)); + return oldbit != 0; +} + +#endif /* !__LIBS_ATOMIC_H__ */ + diff --git a/code/lab8/libs/defs.h b/code/lab8/libs/defs.h new file mode 100644 index 0000000..26b7eb2 --- /dev/null +++ b/code/lab8/libs/defs.h @@ -0,0 +1,79 @@ +#ifndef __LIBS_DEFS_H__ +#define __LIBS_DEFS_H__ + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define __always_inline inline __attribute__((always_inline)) +#define __noinline __attribute__((noinline)) +#define __noreturn __attribute__((noreturn)) + +#define CHAR_BIT 8 + +/* Represents true-or-false values */ +typedef int bool; + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; + +/* off_t is used for file offsets and lengths */ +typedef intptr_t off_t; + +/* used for page numbers */ +typedef size_t ppn_t; + +/* * + * Rounding operations (efficient when n is a power of 2) + * Round down to the nearest multiple of n + * */ +#define ROUNDDOWN(a, n) ({ \ + size_t __a = (size_t)(a); \ + (typeof(a))(__a - __a % (n)); \ + }) + +/* Round up to the nearest multiple of n */ +#define ROUNDUP(a, n) ({ \ + size_t __n = (size_t)(n); \ + (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n)); \ + }) + +/* Round up the result of dividing of n */ +#define ROUNDUP_DIV(a, n) ({ \ +uint32_t __n = (uint32_t)(n); \ +(typeof(a))(((a) + __n - 1) / __n); \ +}) + +/* Return the offset of 'member' relative to the beginning of a struct type */ +#define offsetof(type, member) \ + ((size_t)(&((type *)0)->member)) + +/* * + * to_struct - get the struct from a ptr + * @ptr: a struct pointer of member + * @type: the type of the struct this is embedded in + * @member: the name of the member within the struct + * */ +#define to_struct(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) + +#endif /* !__LIBS_DEFS_H__ */ + diff --git a/code/lab8/libs/dirent.h b/code/lab8/libs/dirent.h new file mode 100644 index 0000000..429e4fe --- /dev/null +++ b/code/lab8/libs/dirent.h @@ -0,0 +1,13 @@ +#ifndef __LIBS_DIRENT_H__ +#define __LIBS_DIRENT_H__ + +#include +#include + +struct dirent { + off_t offset; + char name[FS_MAX_FNAME_LEN + 1]; +}; + +#endif /* !__LIBS_DIRENT_H__ */ + diff --git a/code/lab8/libs/elf.h b/code/lab8/libs/elf.h new file mode 100644 index 0000000..8678f10 --- /dev/null +++ b/code/lab8/libs/elf.h @@ -0,0 +1,48 @@ +#ifndef __LIBS_ELF_H__ +#define __LIBS_ELF_H__ + +#include + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +/* file header */ +struct elfhdr { + uint32_t e_magic; // must equal ELF_MAGIC + uint8_t e_elf[12]; + uint16_t e_type; // 1=relocatable, 2=executable, 3=shared object, 4=core image + uint16_t e_machine; // 3=x86, 4=68K, etc. + uint32_t e_version; // file version, always 1 + uint32_t e_entry; // entry point if executable + uint32_t e_phoff; // file position of program header or 0 + uint32_t e_shoff; // file position of section header or 0 + uint32_t e_flags; // architecture-specific flags, usually 0 + uint16_t e_ehsize; // size of this elf header + uint16_t e_phentsize; // size of an entry in program header + uint16_t e_phnum; // number of entries in program header or 0 + uint16_t e_shentsize; // size of an entry in section header + uint16_t e_shnum; // number of entries in section header or 0 + uint16_t e_shstrndx; // section number that contains section name strings +}; + +/* program section header */ +struct proghdr { + uint32_t p_type; // loadable code or data, dynamic linking info,etc. + uint32_t p_offset; // file offset of segment + uint32_t p_va; // virtual address to map segment + uint32_t p_pa; // physical address, not used + uint32_t p_filesz; // size of segment in file + uint32_t p_memsz; // size of segment in memory (bigger if contains bss) + uint32_t p_flags; // read/write/execute bits + uint32_t p_align; // required alignment, invariably hardware page size +}; + +/* values for Proghdr::p_type */ +#define ELF_PT_LOAD 1 + +/* flag bits for Proghdr::p_flags */ +#define ELF_PF_X 1 +#define ELF_PF_W 2 +#define ELF_PF_R 4 + +#endif /* !__LIBS_ELF_H__ */ + diff --git a/code/lab8/libs/error.h b/code/lab8/libs/error.h new file mode 100644 index 0000000..ddad593 --- /dev/null +++ b/code/lab8/libs/error.h @@ -0,0 +1,33 @@ +#ifndef __LIBS_ERROR_H__ +#define __LIBS_ERROR_H__ + +/* kernel error codes -- keep in sync with list in lib/printfmt.c */ +#define E_UNSPECIFIED 1 // Unspecified or unknown problem +#define E_BAD_PROC 2 // Process doesn't exist or otherwise +#define E_INVAL 3 // Invalid parameter +#define E_NO_MEM 4 // Request failed due to memory shortage +#define E_NO_FREE_PROC 5 // Attempt to create a new process beyond +#define E_FAULT 6 // Memory fault +#define E_SWAP_FAULT 7 // SWAP READ/WRITE fault +#define E_INVAL_ELF 8 // Invalid elf file +#define E_KILLED 9 // Process is killed +#define E_PANIC 10 // Panic Failure +#define E_TIMEOUT 11 // Timeout +#define E_TOO_BIG 12 // Argument is Too Big +#define E_NO_DEV 13 // No such Device +#define E_NA_DEV 14 // Device Not Available +#define E_BUSY 15 // Device/File is Busy +#define E_NOENT 16 // No Such File or Directory +#define E_ISDIR 17 // Is a Directory +#define E_NOTDIR 18 // Not a Directory +#define E_XDEV 19 // Cross Device-Link +#define E_UNIMP 20 // Unimplemented Feature +#define E_SEEK 21 // Illegal Seek +#define E_MAX_OPEN 22 // Too Many Files are Open +#define E_EXISTS 23 // File/Directory Already Exists +#define E_NOTEMPTY 24 // Directory is Not Empty +/* the maximum allowed */ +#define MAXERROR 24 + +#endif /* !__LIBS_ERROR_H__ */ + diff --git a/code/lab8/libs/hash.c b/code/lab8/libs/hash.c new file mode 100644 index 0000000..61bcd88 --- /dev/null +++ b/code/lab8/libs/hash.c @@ -0,0 +1,18 @@ +#include + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL + +/* * + * hash32 - generate a hash value in the range [0, 2^@bits - 1] + * @val: the input value + * @bits: the number of bits in a return value + * + * High bits are more random, so we use them. + * */ +uint32_t +hash32(uint32_t val, unsigned int bits) { + uint32_t hash = val * GOLDEN_RATIO_PRIME_32; + return (hash >> (32 - bits)); +} + diff --git a/code/lab8/libs/list.h b/code/lab8/libs/list.h new file mode 100644 index 0000000..3dbf772 --- /dev/null +++ b/code/lab8/libs/list.h @@ -0,0 +1,163 @@ +#ifndef __LIBS_LIST_H__ +#define __LIBS_LIST_H__ + +#ifndef __ASSEMBLER__ + +#include + +/* * + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when manipulating + * whole lists rather than single entries, as sometimes we already know + * the next/prev entries and we can generate better code by using them + * directly rather than using the generic single-entry routines. + * */ + +struct list_entry { + struct list_entry *prev, *next; +}; + +typedef struct list_entry list_entry_t; + +static inline void list_init(list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline)); +static inline void list_del(list_entry_t *listelm) __attribute__((always_inline)); +static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline)); +static inline bool list_empty(list_entry_t *list) __attribute__((always_inline)); +static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline)); +static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline)); + +static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); +static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline)); + +/* * + * list_init - initialize a new entry + * @elm: new entry to be initialized + * */ +static inline void +list_init(list_entry_t *elm) { + elm->prev = elm->next = elm; +} + +/* * + * list_add - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add(list_entry_t *listelm, list_entry_t *elm) { + list_add_after(listelm, elm); +} + +/* * + * list_add_before - add a new entry + * @listelm: list head to add before + * @elm: new entry to be added + * + * Insert the new element @elm *before* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_before(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm->prev, listelm); +} + +/* * + * list_add_after - add a new entry + * @listelm: list head to add after + * @elm: new entry to be added + * + * Insert the new element @elm *after* the element @listelm which + * is already in the list. + * */ +static inline void +list_add_after(list_entry_t *listelm, list_entry_t *elm) { + __list_add(elm, listelm, listelm->next); +} + +/* * + * list_del - deletes entry from list + * @listelm: the element to delete from the list + * + * Note: list_empty() on @listelm does not return true after this, the entry is + * in an undefined state. + * */ +static inline void +list_del(list_entry_t *listelm) { + __list_del(listelm->prev, listelm->next); +} + +/* * + * list_del_init - deletes entry from list and reinitialize it. + * @listelm: the element to delete from the list. + * + * Note: list_empty() on @listelm returns true after this. + * */ +static inline void +list_del_init(list_entry_t *listelm) { + list_del(listelm); + list_init(listelm); +} + +/* * + * list_empty - tests whether a list is empty + * @list: the list to test. + * */ +static inline bool +list_empty(list_entry_t *list) { + return list->next == list; +} + +/* * + * list_next - get the next entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_next(list_entry_t *listelm) { + return listelm->next; +} + +/* * + * list_prev - get the previous entry + * @listelm: the list head + **/ +static inline list_entry_t * +list_prev(list_entry_t *listelm) { + return listelm->prev; +} + +/* * + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) { + prev->next = next->prev = elm; + elm->next = next; + elm->prev = prev; +} + +/* * + * Delete a list entry by making the prev/next entries point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + * */ +static inline void +__list_del(list_entry_t *prev, list_entry_t *next) { + prev->next = next; + next->prev = prev; +} + +#endif /* !__ASSEMBLER__ */ + +#endif /* !__LIBS_LIST_H__ */ + diff --git a/code/lab8/libs/printfmt.c b/code/lab8/libs/printfmt.c new file mode 100644 index 0000000..888690a --- /dev/null +++ b/code/lab8/libs/printfmt.c @@ -0,0 +1,359 @@ +#include +#include +#include +#include +#include +#include + +/* * + * Space or zero padding and a field width are supported for the numeric + * formats only. + * + * The special format %e takes an integer error code + * and prints a string describing the error. + * The integer may be positive or negative, + * so that -E_NO_MEM and E_NO_MEM are equivalent. + * */ + +static const char * const error_string[MAXERROR + 1] = { + [0] NULL, + [E_UNSPECIFIED] "unspecified error", + [E_BAD_PROC] "bad process", + [E_INVAL] "invalid parameter", + [E_NO_MEM] "out of memory", + [E_NO_FREE_PROC] "out of processes", + [E_FAULT] "segmentation fault", + [E_INVAL_ELF] "invalid elf file", + [E_KILLED] "process is killed", + [E_PANIC] "panic failure", + [E_NO_DEV] "no such device", + [E_NA_DEV] "device not available", + [E_BUSY] "device/file is busy", + [E_NOENT] "no such file or directory", + [E_ISDIR] "is a directory", + [E_NOTDIR] "not a directory", + [E_XDEV] "cross device link", + [E_UNIMP] "unimplemented feature", + [E_SEEK] "illegal seek", + [E_MAX_OPEN] "too many files are open", + [E_EXISTS] "file or directory already exists", + [E_NOTEMPTY] "directory is not empty", +}; + +/* * + * printnum - print a number (base <= 16) in reverse order + * @putch: specified putch function, print a single character + * @fd: file descriptor + * @putdat: used by @putch function + * @num: the number will be printed + * @base: base for print, must be in [1, 16] + * @width: maximum number of digits, if the actual width is less than @width, use @padc instead + * @padc: character that padded on the left if the actual width is less than @width + * */ +static void +printnum(void (*putch)(int, void*, int), int fd, void *putdat, + unsigned long long num, unsigned base, int width, int padc) { + unsigned long long result = num; + unsigned mod = do_div(result, base); + + // first recursively print all preceding (more significant) digits + if (num >= base) { + printnum(putch, fd, putdat, result, base, width - 1, padc); + } else { + // print any needed pad characters before first digit + while (-- width > 0) + putch(padc, putdat, fd); + } + // then print this (the least significant) digit + putch("0123456789abcdef"[mod], putdat, fd); +} + +/* * + * getuint - get an unsigned int of various possible sizes from a varargs list + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static unsigned long long +getuint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, unsigned long long); + } + else if (lflag) { + return va_arg(*ap, unsigned long); + } + else { + return va_arg(*ap, unsigned int); + } +} + +/* * + * getint - same as getuint but signed, we can't use getuint because of sign extension + * @ap: a varargs list pointer + * @lflag: determines the size of the vararg that @ap points to + * */ +static long long +getint(va_list *ap, int lflag) { + if (lflag >= 2) { + return va_arg(*ap, long long); + } + else if (lflag) { + return va_arg(*ap, long); + } + else { + return va_arg(*ap, int); + } +} + +/* * + * printfmt - format a string and print it by using putch + * @putch: specified putch function, print a single character + * @fd: file descriptor + * @putdat: used by @putch function + * @fmt: the format string to use + * */ +void +printfmt(void (*putch)(int, void*, int), int fd, void *putdat, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vprintfmt(putch, fd, putdat, fmt, ap); + va_end(ap); +} + +/* * + * vprintfmt - format a string and print it by using putch, it's called with a va_list + * instead of a variable number of arguments + * @fd: file descriptor + * @putch: specified putch function, print a single character + * @putdat: used by @putch function + * @fmt: the format string to use + * @ap: arguments for the format string + * + * Call this function if you are already dealing with a va_list. + * Or you probably want printfmt() instead. + * */ +void +vprintfmt(void (*putch)(int, void*, int), int fd, void *putdat, const char *fmt, va_list ap) { + register const char *p; + register int ch, err; + unsigned long long num; + int base, width, precision, lflag, altflag; + + while (1) { + while ((ch = *(unsigned char *)fmt ++) != '%') { + if (ch == '\0') { + return; + } + putch(ch, putdat, fd); + } + + // Process a %-escape sequence + char padc = ' '; + width = precision = -1; + lflag = altflag = 0; + + reswitch: + switch (ch = *(unsigned char *)fmt ++) { + + // flag to pad on the right + case '-': + padc = '-'; + goto reswitch; + + // flag to pad with 0's instead of spaces + case '0': + padc = '0'; + goto reswitch; + + // width field + case '1' ... '9': + for (precision = 0; ; ++ fmt) { + precision = precision * 10 + ch - '0'; + ch = *fmt; + if (ch < '0' || ch > '9') { + break; + } + } + goto process_precision; + + case '*': + precision = va_arg(ap, int); + goto process_precision; + + case '.': + if (width < 0) + width = 0; + goto reswitch; + + case '#': + altflag = 1; + goto reswitch; + + process_precision: + if (width < 0) + width = precision, precision = -1; + goto reswitch; + + // long flag (doubled for long long) + case 'l': + lflag ++; + goto reswitch; + + // character + case 'c': + putch(va_arg(ap, int), putdat, fd); + break; + + // error message + case 'e': + err = va_arg(ap, int); + if (err < 0) { + err = -err; + } + if (err > MAXERROR || (p = error_string[err]) == NULL) { + printfmt(putch, fd, putdat, "error %d", err); + } + else { + printfmt(putch, fd, putdat, "%s", p); + } + break; + + // string + case 's': + if ((p = va_arg(ap, char *)) == NULL) { + p = "(null)"; + } + if (width > 0 && padc != '-') { + for (width -= strnlen(p, precision); width > 0; width --) { + putch(padc, putdat, fd); + } + } + for (; (ch = *p ++) != '\0' && (precision < 0 || -- precision >= 0); width --) { + if (altflag && (ch < ' ' || ch > '~')) { + putch('?', putdat, fd); + } + else { + putch(ch, putdat, fd); + } + } + for (; width > 0; width --) { + putch(' ', putdat, fd); + } + break; + + // (signed) decimal + case 'd': + num = getint(&ap, lflag); + if ((long long)num < 0) { + putch('-', putdat, fd); + num = -(long long)num; + } + base = 10; + goto number; + + // unsigned decimal + case 'u': + num = getuint(&ap, lflag); + base = 10; + goto number; + + // (unsigned) octal + case 'o': + num = getuint(&ap, lflag); + base = 8; + goto number; + + // pointer + case 'p': + putch('0', putdat, fd); + putch('x', putdat, fd); + num = (unsigned long long)(uintptr_t)va_arg(ap, void *); + base = 16; + goto number; + + // (unsigned) hexadecimal + case 'x': + num = getuint(&ap, lflag); + base = 16; + number: + printnum(putch, fd, putdat, num, base, width, padc); + break; + + // escaped '%' character + case '%': + putch(ch, putdat, fd); + break; + + // unrecognized escape sequence - just print it literally + default: + putch('%', putdat, fd); + for (fmt --; fmt[-1] != '%'; fmt --) + /* do nothing */; + break; + } + } +} + +/* sprintbuf is used to save enough information of a buffer */ +struct sprintbuf { + char *buf; // address pointer points to the first unused memory + char *ebuf; // points the end of the buffer + int cnt; // the number of characters that have been placed in this buffer +}; + +/* * + * sprintputch - 'print' a single character in a buffer + * @ch: the character will be printed + * @b: the buffer to place the character @ch + * */ +static void +sprintputch(int ch, struct sprintbuf *b) { + b->cnt ++; + if (b->buf < b->ebuf) { + *b->buf ++ = ch; + } +} + +/* * + * snprintf - format a string and place it in a buffer + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * */ +int +snprintf(char *str, size_t size, const char *fmt, ...) { + va_list ap; + int cnt; + va_start(ap, fmt); + cnt = vsnprintf(str, size, fmt, ap); + va_end(ap); + return cnt; +} + +/* * + * vsnprintf - format a string and place it in a buffer, it's called with a va_list + * instead of a variable number of arguments + * @str: the buffer to place the result into + * @size: the size of buffer, including the trailing null space + * @fmt: the format string to use + * @ap: arguments for the format string + * + * The return value is the number of characters which would be generated for the + * given input, excluding the trailing '\0'. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want snprintf() instead. + * */ +int +vsnprintf(char *str, size_t size, const char *fmt, va_list ap) { + struct sprintbuf b = {str, str + size - 1, 0}; + if (str == NULL || b.buf > b.ebuf) { + return -E_INVAL; + } + // print the string to the buffer + vprintfmt((void*)sprintputch, NO_FD, &b, fmt, ap); + // null terminate the buffer + *b.buf = '\0'; + return b.cnt; +} + diff --git a/code/lab8/libs/rand.c b/code/lab8/libs/rand.c new file mode 100644 index 0000000..2a2c4e7 --- /dev/null +++ b/code/lab8/libs/rand.c @@ -0,0 +1,26 @@ +#include +#include + +static unsigned long long next = 1; + +/* * + * rand - returns a pseudo-random integer + * + * The rand() function return a value in the range [0, RAND_MAX]. + * */ +int +rand(void) { + next = (next * 0x5DEECE66DLL + 0xBLL) & ((1LL << 48) - 1); + unsigned long long result = (next >> 12); + return (int)do_div(result, RAND_MAX + 1); +} + +/* * + * srand - seed the random number generator with the given number + * @seed: the required seed number + * */ +void +srand(unsigned int seed) { + next = seed; +} + diff --git a/code/lab8/libs/skew_heap.h b/code/lab8/libs/skew_heap.h new file mode 100644 index 0000000..0c216b8 --- /dev/null +++ b/code/lab8/libs/skew_heap.h @@ -0,0 +1,87 @@ +#ifndef __LIBS_SKEW_HEAP_H__ +#define __LIBS_SKEW_HEAP_H__ + +struct skew_heap_entry { + struct skew_heap_entry *parent, *left, *right; +}; + +typedef struct skew_heap_entry skew_heap_entry_t; + +typedef int(*compare_f)(void *a, void *b); + +static inline void skew_heap_init(skew_heap_entry_t *a) __attribute__((always_inline)); +static inline skew_heap_entry_t *skew_heap_merge( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp); +static inline skew_heap_entry_t *skew_heap_insert( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) __attribute__((always_inline)); +static inline skew_heap_entry_t *skew_heap_remove( + skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) __attribute__((always_inline)); + +static inline void +skew_heap_init(skew_heap_entry_t *a) +{ + a->left = a->right = a->parent = NULL; +} + +static inline skew_heap_entry_t * +skew_heap_merge(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + if (a == NULL) return b; + else if (b == NULL) return a; + + skew_heap_entry_t *l, *r; + if (comp(a, b) == -1) + { + r = a->left; + l = skew_heap_merge(a->right, b, comp); + + a->left = l; + a->right = r; + if (l) l->parent = a; + + return a; + } + else + { + r = b->left; + l = skew_heap_merge(a, b->right, comp); + + b->left = l; + b->right = r; + if (l) l->parent = b; + + return b; + } +} + +static inline skew_heap_entry_t * +skew_heap_insert(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + skew_heap_init(b); + return skew_heap_merge(a, b, comp); +} + +static inline skew_heap_entry_t * +skew_heap_remove(skew_heap_entry_t *a, skew_heap_entry_t *b, + compare_f comp) +{ + skew_heap_entry_t *p = b->parent; + skew_heap_entry_t *rep = skew_heap_merge(b->left, b->right, comp); + if (rep) rep->parent = p; + + if (p) + { + if (p->left == b) + p->left = rep; + else p->right = rep; + return a; + } + else return rep; +} + +#endif /* !__LIBS_SKEW_HEAP_H__ */ diff --git a/code/lab8/libs/stat.h b/code/lab8/libs/stat.h new file mode 100644 index 0000000..41a392d --- /dev/null +++ b/code/lab8/libs/stat.h @@ -0,0 +1,27 @@ +#ifndef __LIBS_STAT_H__ +#define __LIBS_STAT_H__ + +#include + +struct stat { + uint32_t st_mode; // protection mode and file type + size_t st_nlinks; // number of hard links + size_t st_blocks; // number of blocks file is using + size_t st_size; // file size (bytes) +}; + +#define S_IFMT 070000 // mask for type of file +#define S_IFREG 010000 // ordinary regular file +#define S_IFDIR 020000 // directory +#define S_IFLNK 030000 // symbolic link +#define S_IFCHR 040000 // character device +#define S_IFBLK 050000 // block device + +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) // regular file +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) // directory +#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) // symlink +#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) // char device +#define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) // block device + +#endif /* !__LIBS_STAT_H__ */ + diff --git a/code/lab8/libs/stdarg.h b/code/lab8/libs/stdarg.h new file mode 100644 index 0000000..f6e0758 --- /dev/null +++ b/code/lab8/libs/stdarg.h @@ -0,0 +1,12 @@ +#ifndef __LIBS_STDARG_H__ +#define __LIBS_STDARG_H__ + +/* compiler provides size of save area */ +typedef __builtin_va_list va_list; + +#define va_start(ap, last) (__builtin_va_start(ap, last)) +#define va_arg(ap, type) (__builtin_va_arg(ap, type)) +#define va_end(ap) /*nothing*/ + +#endif /* !__LIBS_STDARG_H__ */ + diff --git a/code/lab8/libs/stdio.h b/code/lab8/libs/stdio.h new file mode 100644 index 0000000..ab03960 --- /dev/null +++ b/code/lab8/libs/stdio.h @@ -0,0 +1,24 @@ +#ifndef __LIBS_STDIO_H__ +#define __LIBS_STDIO_H__ + +#include +#include + +/* kern/libs/stdio.c */ +int cprintf(const char *fmt, ...); +int vcprintf(const char *fmt, va_list ap); +void cputchar(int c); +int cputs(const char *str); +int getchar(void); + +/* kern/libs/readline.c */ +char *readline(const char *prompt); + +/* libs/printfmt.c */ +void printfmt(void (*putch)(int, void *, int), int fd, void *putdat, const char *fmt, ...); +void vprintfmt(void (*putch)(int, void *, int), int fd, void *putdat, const char *fmt, va_list ap); +int snprintf(char *str, size_t size, const char *fmt, ...); +int vsnprintf(char *str, size_t size, const char *fmt, va_list ap); + +#endif /* !__LIBS_STDIO_H__ */ + diff --git a/code/lab8/libs/stdlib.h b/code/lab8/libs/stdlib.h new file mode 100644 index 0000000..51878ef --- /dev/null +++ b/code/lab8/libs/stdlib.h @@ -0,0 +1,17 @@ +#ifndef __LIBS_STDLIB_H__ +#define __LIBS_STDLIB_H__ + +#include + +/* the largest number rand will return */ +#define RAND_MAX 2147483647UL + +/* libs/rand.c */ +int rand(void); +void srand(unsigned int seed); + +/* libs/hash.c */ +uint32_t hash32(uint32_t val, unsigned int bits); + +#endif /* !__LIBS_RAND_H__ */ + diff --git a/code/lab8/libs/string.c b/code/lab8/libs/string.c new file mode 100644 index 0000000..9180a9a --- /dev/null +++ b/code/lab8/libs/string.c @@ -0,0 +1,380 @@ +#include +#include + +/* * + * strlen - calculate the length of the string @s, not including + * the terminating '\0' character. + * @s: the input string + * + * The strlen() function returns the length of string @s. + * */ +size_t +strlen(const char *s) { + size_t cnt = 0; + while (*s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strnlen - calculate the length of the string @s, not including + * the terminating '\0' char acter, but at most @len. + * @s: the input string + * @len: the max-length that function will scan + * + * Note that, this function looks only at the first @len characters + * at @s, and never beyond @s + @len. + * + * The return value is strlen(s), if that is less than @len, or + * @len if there is no '\0' character among the first @len characters + * pointed by @s. + * */ +size_t +strnlen(const char *s, size_t len) { + size_t cnt = 0; + while (cnt < len && *s ++ != '\0') { + cnt ++; + } + return cnt; +} + +/* * + * strcat - appends a copy of the @src string to the @dst string. The terminating null + * character in @dst is overwritten by the first character of @src, and a new null-character + * is appended at the end of the new string formed by the concatenation of both in @dst. + * @dst: pointer to the @dst array, which should be large enough to contain the concatenated + * resulting string. + * @src: string to be appended, this should not overlap @dst + * */ +char * +strcat(char *dst, const char *src) { + return strcpy(dst + strlen(dst), src); +} + +/* * + * strcpy - copies the string pointed by @src into the array pointed by @dst, + * including the terminating null character. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * + * The return value is @dst. + * + * To avoid overflows, the size of array pointed by @dst should be long enough to + * contain the same string as @src (including the terminating null character), and + * should not overlap in memory with @src. + * */ +char * +strcpy(char *dst, const char *src) { +#ifdef __HAVE_ARCH_STRCPY + return __strcpy(dst, src); +#else + char *p = dst; + while ((*p ++ = *src ++) != '\0') + /* nothing */; + return dst; +#endif /* __HAVE_ARCH_STRCPY */ +} + +/* * + * strncpy - copies the first @len characters of @src to @dst. If the end of string @src + * if found before @len characters have been copied, @dst is padded with '\0' until a + * total of @len characters have been written to it. + * @dst: pointer to the destination array where the content is to be copied + * @src: string to be copied + * @len: maximum number of characters to be copied from @src + * + * The return value is @dst + * */ +char * +strncpy(char *dst, const char *src, size_t len) { + char *p = dst; + while (len > 0) { + if ((*p = *src) != '\0') { + src ++; + } + p ++, len --; + } + return dst; +} + +/* * + * strcmp - compares the string @s1 and @s2 + * @s1: string to be compared + * @s2: string to be compared + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ or until a terminanting null-character is reached. + * + * Returns an integral value indicating the relationship between the strings: + * - A zero value indicates that both strings are equal; + * - A value greater than zero indicates that the first character that does + * not match has a greater value in @s1 than in @s2; + * - And a value less than zero indicates the opposite. + * */ +int +strcmp(const char *s1, const char *s2) { +#ifdef __HAVE_ARCH_STRCMP + return __strcmp(s1, s2); +#else + while (*s1 != '\0' && *s1 == *s2) { + s1 ++, s2 ++; + } + return (int)((unsigned char)*s1 - (unsigned char)*s2); +#endif /* __HAVE_ARCH_STRCMP */ +} + +/* * + * strncmp - compares up to @n characters of the string @s1 to those of the string @s2 + * @s1: string to be compared + * @s2: string to be compared + * @n: maximum number of characters to compare + * + * This function starts comparing the first character of each string. If + * they are equal to each other, it continues with the following pairs until + * the characters differ, until a terminating null-character is reached, or + * until @n characters match in both strings, whichever happens first. + * */ +int +strncmp(const char *s1, const char *s2, size_t n) { + while (n > 0 && *s1 != '\0' && *s1 == *s2) { + n --, s1 ++, s2 ++; + } + return (n == 0) ? 0 : (int)((unsigned char)*s1 - (unsigned char)*s2); +} + +/* * + * strchr - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strchr() function returns a pointer to the first occurrence of + * character in @s. If the value is not found, the function returns 'NULL'. + * */ +char * +strchr(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + return (char *)s; + } + s ++; + } + return NULL; +} + +/* * + * strfind - locates first occurrence of character in string + * @s: the input string + * @c: character to be located + * + * The strfind() function is like strchr() except that if @c is + * not found in @s, then it returns a pointer to the null byte at the + * end of @s, rather than 'NULL'. + * */ +char * +strfind(const char *s, char c) { + while (*s != '\0') { + if (*s == c) { + break; + } + s ++; + } + return (char *)s; +} + +/* * + * strtol - converts string to long integer + * @s: the input string that contains the representation of an integer number + * @endptr: reference to an object of type char *, whose value is set by the + * function to the next character in @s after the numerical value. This + * parameter can also be a null pointer, in which case it is not used. + * @base: x + * + * The function first discards as many whitespace characters as necessary until + * the first non-whitespace character is found. Then, starting from this character, + * takes as many characters as possible that are valid following a syntax that + * depends on the base parameter, and interprets them as a numerical value. Finally, + * a pointer to the first character following the integer representation in @s + * is stored in the object pointed by @endptr. + * + * If the value of base is zero, the syntax expected is similar to that of + * integer constants, which is formed by a succession of: + * - An optional plus or minus sign; + * - An optional prefix indicating octal or hexadecimal base ("0" or "0x" respectively) + * - A sequence of decimal digits (if no base prefix was specified) or either octal + * or hexadecimal digits if a specific prefix is present + * + * If the base value is between 2 and 36, the format expected for the integral number + * is a succession of the valid digits and/or letters needed to represent integers of + * the specified radix (starting from '0' and up to 'z'/'Z' for radix 36). The + * sequence may optionally be preceded by a plus or minus sign and, if base is 16, + * an optional "0x" or "0X" prefix. + * + * The strtol() function returns the converted integral number as a long int value. + * */ +long +strtol(const char *s, char **endptr, int base) { + int neg = 0; + long val = 0; + + // gobble initial whitespace + while (*s == ' ' || *s == '\t') { + s ++; + } + + // plus/minus sign + if (*s == '+') { + s ++; + } + else if (*s == '-') { + s ++, neg = 1; + } + + // hex or octal base prefix + if ((base == 0 || base == 16) && (s[0] == '0' && s[1] == 'x')) { + s += 2, base = 16; + } + else if (base == 0 && s[0] == '0') { + s ++, base = 8; + } + else if (base == 0) { + base = 10; + } + + // digits + while (1) { + int dig; + + if (*s >= '0' && *s <= '9') { + dig = *s - '0'; + } + else if (*s >= 'a' && *s <= 'z') { + dig = *s - 'a' + 10; + } + else if (*s >= 'A' && *s <= 'Z') { + dig = *s - 'A' + 10; + } + else { + break; + } + if (dig >= base) { + break; + } + s ++, val = (val * base) + dig; + // we don't properly detect overflow! + } + + if (endptr) { + *endptr = (char *) s; + } + return (neg ? -val : val); +} + +/* * + * memset - sets the first @n bytes of the memory area pointed by @s + * to the specified value @c. + * @s: pointer the the memory area to fill + * @c: value to set + * @n: number of bytes to be set to the value + * + * The memset() function returns @s. + * */ +void * +memset(void *s, char c, size_t n) { +#ifdef __HAVE_ARCH_MEMSET + return __memset(s, c, n); +#else + char *p = s; + while (n -- > 0) { + *p ++ = c; + } + return s; +#endif /* __HAVE_ARCH_MEMSET */ +} + +/* * + * memmove - copies the values of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. @src and @dst are allowed to overlap. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memmove() function returns @dst. + * */ +void * +memmove(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMMOVE + return __memmove(dst, src, n); +#else + const char *s = src; + char *d = dst; + if (s < d && s + n > d) { + s += n, d += n; + while (n -- > 0) { + *-- d = *-- s; + } + } else { + while (n -- > 0) { + *d ++ = *s ++; + } + } + return dst; +#endif /* __HAVE_ARCH_MEMMOVE */ +} + +/* * + * memcpy - copies the value of @n bytes from the location pointed by @src to + * the memory area pointed by @dst. + * @dst pointer to the destination array where the content is to be copied + * @src pointer to the source of data to by copied + * @n: number of bytes to copy + * + * The memcpy() returns @dst. + * + * Note that, the function does not check any terminating null character in @src, + * it always copies exactly @n bytes. To avoid overflows, the size of arrays pointed + * by both @src and @dst, should be at least @n bytes, and should not overlap + * (for overlapping memory area, memmove is a safer approach). + * */ +void * +memcpy(void *dst, const void *src, size_t n) { +#ifdef __HAVE_ARCH_MEMCPY + return __memcpy(dst, src, n); +#else + const char *s = src; + char *d = dst; + while (n -- > 0) { + *d ++ = *s ++; + } + return dst; +#endif /* __HAVE_ARCH_MEMCPY */ +} + +/* * + * memcmp - compares two blocks of memory + * @v1: pointer to block of memory + * @v2: pointer to block of memory + * @n: number of bytes to compare + * + * The memcmp() functions returns an integral value indicating the + * relationship between the content of the memory blocks: + * - A zero value indicates that the contents of both memory blocks are equal; + * - A value greater than zero indicates that the first byte that does not + * match in both memory blocks has a greater value in @v1 than in @v2 + * as if evaluated as unsigned char values; + * - And a value less than zero indicates the opposite. + * */ +int +memcmp(const void *v1, const void *v2, size_t n) { + const char *s1 = (const char *)v1; + const char *s2 = (const char *)v2; + while (n -- > 0) { + if (*s1 != *s2) { + return (int)((unsigned char)*s1 - (unsigned char)*s2); + } + s1 ++, s2 ++; + } + return 0; +} + diff --git a/code/lab8/libs/string.h b/code/lab8/libs/string.h new file mode 100644 index 0000000..76c6f3b --- /dev/null +++ b/code/lab8/libs/string.h @@ -0,0 +1,28 @@ +#ifndef __LIBS_STRING_H__ +#define __LIBS_STRING_H__ + +#include + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t len); + +char *strcpy(char *dst, const char *src); +char *strncpy(char *dst, const char *src, size_t len); +char *strcat(char *dst, const char *src); +char *strdup(const char *src); +char *stradd(const char *src1, const char *src2); + +int strcmp(const char *s1, const char *s2); +int strncmp(const char *s1, const char *s2, size_t n); + +char *strchr(const char *s, char c); +char *strfind(const char *s, char c); +long strtol(const char *s, char **endptr, int base); + +void *memset(void *s, char c, size_t n); +void *memmove(void *dst, const void *src, size_t n); +void *memcpy(void *dst, const void *src, size_t n); +int memcmp(const void *v1, const void *v2, size_t n); + +#endif /* !__LIBS_STRING_H__ */ + diff --git a/code/lab8/libs/unistd.h b/code/lab8/libs/unistd.h new file mode 100644 index 0000000..8599be6 --- /dev/null +++ b/code/lab8/libs/unistd.h @@ -0,0 +1,68 @@ +#ifndef __LIBS_UNISTD_H__ +#define __LIBS_UNISTD_H__ + +#define T_SYSCALL 0x80 + +/* syscall number */ +#define SYS_exit 1 +#define SYS_fork 2 +#define SYS_wait 3 +#define SYS_exec 4 +#define SYS_clone 5 +#define SYS_yield 10 +#define SYS_sleep 11 +#define SYS_kill 12 +#define SYS_gettime 17 +#define SYS_getpid 18 +#define SYS_mmap 20 +#define SYS_munmap 21 +#define SYS_shmem 22 +#define SYS_putc 30 +#define SYS_pgdir 31 +#define SYS_open 100 +#define SYS_close 101 +#define SYS_read 102 +#define SYS_write 103 +#define SYS_seek 104 +#define SYS_fstat 110 +#define SYS_fsync 111 +#define SYS_getcwd 121 +#define SYS_getdirentry 128 +#define SYS_dup 130 +/* OLNY FOR LAB6 */ +#define SYS_lab6_set_priority 255 + +/* SYS_fork flags */ +#define CLONE_VM 0x00000100 // set if VM shared between processes +#define CLONE_THREAD 0x00000200 // thread group +#define CLONE_FS 0x00000800 // set if shared between processes + +/* VFS flags */ +// flags for open: choose one of these +#define O_RDONLY 0 // open for reading only +#define O_WRONLY 1 // open for writing only +#define O_RDWR 2 // open for reading and writing +// then or in any of these: +#define O_CREAT 0x00000004 // create file if it does not exist +#define O_EXCL 0x00000008 // error if O_CREAT and the file exists +#define O_TRUNC 0x00000010 // truncate file upon open +#define O_APPEND 0x00000020 // append on each write +// additonal related definition +#define O_ACCMODE 3 // mask for O_RDONLY / O_WRONLY / O_RDWR + +#define NO_FD -0x9527 // invalid fd + +/* lseek codes */ +#define LSEEK_SET 0 // seek relative to beginning of file +#define LSEEK_CUR 1 // seek relative to current position in file +#define LSEEK_END 2 // seek relative to end of file + +#define FS_MAX_DNAME_LEN 31 +#define FS_MAX_FNAME_LEN 255 +#define FS_MAX_FPATH_LEN 4095 + +#define EXEC_MAX_ARG_NUM 32 +#define EXEC_MAX_ARG_LEN 4095 + +#endif /* !__LIBS_UNISTD_H__ */ + diff --git a/code/lab8/libs/x86.h b/code/lab8/libs/x86.h new file mode 100644 index 0000000..b5e7011 --- /dev/null +++ b/code/lab8/libs/x86.h @@ -0,0 +1,310 @@ +#ifndef __LIBS_X86_H__ +#define __LIBS_X86_H__ + +#include + +#define do_div(n, base) ({ \ + unsigned long __upper, __low, __high, __mod, __base; \ + __base = (base); \ + asm ("" : "=a" (__low), "=d" (__high) : "A" (n)); \ + __upper = __high; \ + if (__high != 0) { \ + __upper = __high % __base; \ + __high = __high / __base; \ + } \ + asm ("divl %2" : "=a" (__low), "=d" (__mod) \ + : "rm" (__base), "0" (__low), "1" (__upper)); \ + asm ("" : "=A" (n) : "a" (__low), "d" (__high)); \ + __mod; \ + }) + +#define barrier() __asm__ __volatile__ ("" ::: "memory") + +static inline uint8_t inb(uint16_t port) __attribute__((always_inline)); +static inline uint16_t inw(uint16_t port) __attribute__((always_inline)); +static inline void insl(uint32_t port, void *addr, int cnt) __attribute__((always_inline)); +static inline void outb(uint16_t port, uint8_t data) __attribute__((always_inline)); +static inline void outw(uint16_t port, uint16_t data) __attribute__((always_inline)); +static inline void outsl(uint32_t port, const void *addr, int cnt) __attribute__((always_inline)); +static inline uint32_t read_ebp(void) __attribute__((always_inline)); +static inline void breakpoint(void) __attribute__((always_inline)); +static inline uint32_t read_dr(unsigned regnum) __attribute__((always_inline)); +static inline void write_dr(unsigned regnum, uint32_t value) __attribute__((always_inline)); + +/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */ +struct pseudodesc { + uint16_t pd_lim; // Limit + uintptr_t pd_base; // Base address +} __attribute__ ((packed)); + +static inline void lidt(struct pseudodesc *pd) __attribute__((always_inline)); +static inline void sti(void) __attribute__((always_inline)); +static inline void cli(void) __attribute__((always_inline)); +static inline void ltr(uint16_t sel) __attribute__((always_inline)); +static inline uint32_t read_eflags(void) __attribute__((always_inline)); +static inline void write_eflags(uint32_t eflags) __attribute__((always_inline)); +static inline void lcr0(uintptr_t cr0) __attribute__((always_inline)); +static inline void lcr3(uintptr_t cr3) __attribute__((always_inline)); +static inline uintptr_t rcr0(void) __attribute__((always_inline)); +static inline uintptr_t rcr1(void) __attribute__((always_inline)); +static inline uintptr_t rcr2(void) __attribute__((always_inline)); +static inline uintptr_t rcr3(void) __attribute__((always_inline)); +static inline void invlpg(void *addr) __attribute__((always_inline)); + +static inline uint8_t +inb(uint16_t port) { + uint8_t data; + asm volatile ("inb %1, %0" : "=a" (data) : "d" (port) : "memory"); + return data; +} + +static inline uint16_t +inw(uint16_t port) { + uint16_t data; + asm volatile ("inw %1, %0" : "=a" (data) : "d" (port)); + return data; +} + +static inline void +insl(uint32_t port, void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; insl;" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline void +outb(uint16_t port, uint8_t data) { + asm volatile ("outb %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outw(uint16_t port, uint16_t data) { + asm volatile ("outw %0, %1" :: "a" (data), "d" (port) : "memory"); +} + +static inline void +outsl(uint32_t port, const void *addr, int cnt) { + asm volatile ( + "cld;" + "repne; outsl;" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +static inline uint32_t +read_ebp(void) { + uint32_t ebp; + asm volatile ("movl %%ebp, %0" : "=r" (ebp)); + return ebp; +} + +static inline void +breakpoint(void) { + asm volatile ("int $3"); +} + +static inline uint32_t +read_dr(unsigned regnum) { + uint32_t value = 0; + switch (regnum) { + case 0: asm volatile ("movl %%db0, %0" : "=r" (value)); break; + case 1: asm volatile ("movl %%db1, %0" : "=r" (value)); break; + case 2: asm volatile ("movl %%db2, %0" : "=r" (value)); break; + case 3: asm volatile ("movl %%db3, %0" : "=r" (value)); break; + case 6: asm volatile ("movl %%db6, %0" : "=r" (value)); break; + case 7: asm volatile ("movl %%db7, %0" : "=r" (value)); break; + } + return value; +} + +static void +write_dr(unsigned regnum, uint32_t value) { + switch (regnum) { + case 0: asm volatile ("movl %0, %%db0" :: "r" (value)); break; + case 1: asm volatile ("movl %0, %%db1" :: "r" (value)); break; + case 2: asm volatile ("movl %0, %%db2" :: "r" (value)); break; + case 3: asm volatile ("movl %0, %%db3" :: "r" (value)); break; + case 6: asm volatile ("movl %0, %%db6" :: "r" (value)); break; + case 7: asm volatile ("movl %0, %%db7" :: "r" (value)); break; + } +} + +static inline void +lidt(struct pseudodesc *pd) { + asm volatile ("lidt (%0)" :: "r" (pd) : "memory"); +} + +static inline void +sti(void) { + asm volatile ("sti"); +} + +static inline void +cli(void) { + asm volatile ("cli" ::: "memory"); +} + +static inline void +ltr(uint16_t sel) { + asm volatile ("ltr %0" :: "r" (sel) : "memory"); +} + +static inline uint32_t +read_eflags(void) { + uint32_t eflags; + asm volatile ("pushfl; popl %0" : "=r" (eflags)); + return eflags; +} + +static inline void +write_eflags(uint32_t eflags) { + asm volatile ("pushl %0; popfl" :: "r" (eflags)); +} + +static inline void +lcr0(uintptr_t cr0) { + asm volatile ("mov %0, %%cr0" :: "r" (cr0) : "memory"); +} + +static inline void +lcr3(uintptr_t cr3) { + asm volatile ("mov %0, %%cr3" :: "r" (cr3) : "memory"); +} + +static inline uintptr_t +rcr0(void) { + uintptr_t cr0; + asm volatile ("mov %%cr0, %0" : "=r" (cr0) :: "memory"); + return cr0; +} + +static inline uintptr_t +rcr1(void) { + uintptr_t cr1; + asm volatile ("mov %%cr1, %0" : "=r" (cr1) :: "memory"); + return cr1; +} + +static inline uintptr_t +rcr2(void) { + uintptr_t cr2; + asm volatile ("mov %%cr2, %0" : "=r" (cr2) :: "memory"); + return cr2; +} + +static inline uintptr_t +rcr3(void) { + uintptr_t cr3; + asm volatile ("mov %%cr3, %0" : "=r" (cr3) :: "memory"); + return cr3; +} + +static inline void +invlpg(void *addr) { + asm volatile ("invlpg (%0)" :: "r" (addr) : "memory"); +} + +static inline int __strcmp(const char *s1, const char *s2) __attribute__((always_inline)); +static inline char *__strcpy(char *dst, const char *src) __attribute__((always_inline)); +static inline void *__memset(void *s, char c, size_t n) __attribute__((always_inline)); +static inline void *__memmove(void *dst, const void *src, size_t n) __attribute__((always_inline)); +static inline void *__memcpy(void *dst, const void *src, size_t n) __attribute__((always_inline)); + +#ifndef __HAVE_ARCH_STRCMP +#define __HAVE_ARCH_STRCMP +static inline int +__strcmp(const char *s1, const char *s2) { + int d0, d1, ret; + asm volatile ( + "1: lodsb;" + "scasb;" + "jne 2f;" + "testb %%al, %%al;" + "jne 1b;" + "xorl %%eax, %%eax;" + "jmp 3f;" + "2: sbbl %%eax, %%eax;" + "orb $1, %%al;" + "3:" + : "=a" (ret), "=&S" (d0), "=&D" (d1) + : "1" (s1), "2" (s2) + : "memory"); + return ret; +} + +#endif /* __HAVE_ARCH_STRCMP */ + +#ifndef __HAVE_ARCH_STRCPY +#define __HAVE_ARCH_STRCPY +static inline char * +__strcpy(char *dst, const char *src) { + int d0, d1, d2; + asm volatile ( + "1: lodsb;" + "stosb;" + "testb %%al, %%al;" + "jne 1b;" + : "=&S" (d0), "=&D" (d1), "=&a" (d2) + : "0" (src), "1" (dst) : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_STRCPY */ + +#ifndef __HAVE_ARCH_MEMSET +#define __HAVE_ARCH_MEMSET +static inline void * +__memset(void *s, char c, size_t n) { + int d0, d1; + asm volatile ( + "rep; stosb;" + : "=&c" (d0), "=&D" (d1) + : "0" (n), "a" (c), "1" (s) + : "memory"); + return s; +} +#endif /* __HAVE_ARCH_MEMSET */ + +#ifndef __HAVE_ARCH_MEMMOVE +#define __HAVE_ARCH_MEMMOVE +static inline void * +__memmove(void *dst, const void *src, size_t n) { + if (dst < src) { + return __memcpy(dst, src, n); + } + int d0, d1, d2; + asm volatile ( + "std;" + "rep; movsb;" + "cld;" + : "=&c" (d0), "=&S" (d1), "=&D" (d2) + : "0" (n), "1" (n - 1 + src), "2" (n - 1 + dst) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMMOVE */ + +#ifndef __HAVE_ARCH_MEMCPY +#define __HAVE_ARCH_MEMCPY +static inline void * +__memcpy(void *dst, const void *src, size_t n) { + int d0, d1, d2; + asm volatile ( + "rep; movsl;" + "movl %4, %%ecx;" + "andl $3, %%ecx;" + "jz 1f;" + "rep; movsb;" + "1:" + : "=&c" (d0), "=&D" (d1), "=&S" (d2) + : "0" (n / 4), "g" (n), "1" (dst), "2" (src) + : "memory"); + return dst; +} +#endif /* __HAVE_ARCH_MEMCPY */ + +#endif /* !__LIBS_X86_H__ */ + diff --git a/code/lab8/tools/boot.ld b/code/lab8/tools/boot.ld new file mode 100644 index 0000000..dc732b0 --- /dev/null +++ b/code/lab8/tools/boot.ld @@ -0,0 +1,15 @@ +OUTPUT_FORMAT("elf32-i386") +OUTPUT_ARCH(i386) + +SECTIONS { + . = 0x7C00; + + .startup : { + *bootasm.o(.text) + } + + .text : { *(.text) } + .data : { *(.data .rodata) } + + /DISCARD/ : { *(.eh_*) } +} diff --git a/code/lab8/tools/function.mk b/code/lab8/tools/function.mk new file mode 100644 index 0000000..9b8be0c --- /dev/null +++ b/code/lab8/tools/function.mk @@ -0,0 +1,95 @@ +OBJPREFIX := __objs_ + +.SECONDEXPANSION: +# -------------------- function begin -------------------- + +# list all files in some directories: (#directories, #types) +listf = $(filter $(if $(2),$(addprefix %.,$(2)),%),\ + $(wildcard $(addsuffix $(SLASH)*,$(1)))) + +# get .o obj files: (#files[, packet]) +toobj = $(addprefix $(OBJDIR)$(SLASH)$(if $(2),$(2)$(SLASH)),\ + $(addsuffix .o,$(basename $(1)))) + +# get .d dependency files: (#files[, packet]) +todep = $(patsubst %.o,%.d,$(call toobj,$(1),$(2))) + +totarget = $(addprefix $(BINDIR)$(SLASH),$(1)) + +# change $(name) to $(OBJPREFIX)$(name): (#names) +packetname = $(if $(1),$(addprefix $(OBJPREFIX),$(1)),$(OBJPREFIX)) + +# cc compile template, generate rule for dep, obj: (file, cc[, flags, dir]) +define cc_template +$$(call todep,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @$(2) -I$$(dir $(1)) $(3) -MM $$< -MT "$$(patsubst %.d,%.o,$$@) $$@"> $$@ +$$(call toobj,$(1),$(4)): $(1) | $$$$(dir $$$$@) + @echo + cc $$< + $(V)$(2) -I$$(dir $(1)) $(3) -c $$< -o $$@ +ALLOBJS += $$(call toobj,$(1),$(4)) +endef + +# compile file: (#files, cc[, flags, dir]) +define do_cc_compile +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(4)))) +endef + +# add files to packet: (#files, cc[, flags, packet, dir]) +define do_add_files_to_packet +__temp_packet__ := $(call packetname,$(4)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +__temp_objs__ := $(call toobj,$(1),$(5)) +$$(foreach f,$(1),$$(eval $$(call cc_template,$$(f),$(2),$(3),$(5)))) +$$(__temp_packet__) += $$(__temp_objs__) +endef + +# add objs to packet: (#objs, packet) +define do_add_objs_to_packet +__temp_packet__ := $(call packetname,$(2)) +ifeq ($$(origin $$(__temp_packet__)),undefined) +$$(__temp_packet__) := +endif +$$(__temp_packet__) += $(1) +endef + +# add packets and objs to target (target, #packes, #objs[, cc, flags]) +define do_create_target +__temp_target__ = $(call totarget,$(1)) +__temp_objs__ = $$(foreach p,$(call packetname,$(2)),$$($$(p))) $(3) +TARGETS += $$(__temp_target__) +ifneq ($(4),) +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) + $(V)$(4) $(5) $$^ -o $$@ +else +$$(__temp_target__): $$(__temp_objs__) | $$$$(dir $$$$@) +endif +endef + +# finish all +define do_finish_all +ALLDEPS = $$(ALLOBJS:.o=.d) +$$(sort $$(dir $$(ALLOBJS)) $(BINDIR)$(SLASH) $(OBJDIR)$(SLASH)): + @$(MKDIR) $$@ +endef + +# -------------------- function end -------------------- +# compile file: (#files, cc[, flags, dir]) +cc_compile = $(eval $(call do_cc_compile,$(1),$(2),$(3),$(4))) + +# add files to packet: (#files, cc[, flags, packet, dir]) +add_files = $(eval $(call do_add_files_to_packet,$(1),$(2),$(3),$(4),$(5))) + +# add objs to packet: (#objs, packet) +add_objs = $(eval $(call do_add_objs_to_packet,$(1),$(2))) + +# add packets and objs to target (target, #packes, #objs, cc, [, flags]) +create_target = $(eval $(call do_create_target,$(1),$(2),$(3),$(4),$(5))) + +read_packet = $(foreach p,$(call packetname,$(1)),$($(p))) + +add_dependency = $(eval $(1): $(2)) + +finish_all = $(eval $(call do_finish_all)) + diff --git a/code/lab8/tools/gdbinit b/code/lab8/tools/gdbinit new file mode 100644 index 0000000..df5df85 --- /dev/null +++ b/code/lab8/tools/gdbinit @@ -0,0 +1,3 @@ +file bin/kernel +target remote :1234 +break kern_init diff --git a/code/lab8/tools/grade.sh b/code/lab8/tools/grade.sh new file mode 100644 index 0000000..de10321 --- /dev/null +++ b/code/lab8/tools/grade.sh @@ -0,0 +1,636 @@ +#!/bin/sh + +verbose=false +if [ "x$1" = "x-v" ]; then + verbose=true + out=/dev/stdout + err=/dev/stderr +else + out=/dev/null + err=/dev/null +fi + +## make & makeopts +if gmake --version > /dev/null 2>&1; then + make=gmake; +else + make=make; +fi + +makeopts="--quiet --no-print-directory -j" + +make_print() { + echo `$make $makeopts print-$1` +} + +## command tools +awk='awk' +bc='bc' +date='date' +grep='grep' +rm='rm -f' +sed='sed' + +## symbol table +sym_table='obj/kernel.sym' + +## gdb & gdbopts +gdb="$(make_print GDB)" +gdbport='1234' + +gdb_in="$(make_print GRADE_GDB_IN)" + +## qemu & qemuopts +qemu="$(make_print qemu)" + +qemu_out="$(make_print GRADE_QEMU_OUT)" + +if $qemu -nographic -help | grep -q '^-gdb'; then + qemugdb="-gdb tcp::$gdbport" +else + qemugdb="-s -p $gdbport" +fi + +## default variables +default_timeout=30 +default_pts=5 + +pts=5 +part=0 +part_pos=0 +total=0 +total_pos=0 + +## default functions +update_score() { + total=`expr $total + $part` + total_pos=`expr $total_pos + $part_pos` + part=0 + part_pos=0 +} + +get_time() { + echo `$date +%s.%N 2> /dev/null` +} + +show_part() { + echo "Part $1 Score: $part/$part_pos" + echo + update_score +} + +show_final() { + update_score + echo "Total Score: $total/$total_pos" + if [ $total -lt $total_pos ]; then + exit 1 + fi +} + +show_time() { + t1=$(get_time) + time=`echo "scale=1; ($t1-$t0)/1" | $sed 's/.N/.0/g' | $bc 2> /dev/null` + echo "(${time}s)" +} + +show_build_tag() { + echo "$1:" | $awk '{printf "%-24s ", $0}' +} + +show_check_tag() { + echo "$1:" | $awk '{printf " -%-40s ", $0}' +} + +show_msg() { + echo $1 + shift + if [ $# -gt 0 ]; then + echo "$@" | awk '{printf " %s\n", $0}' + echo + fi +} + +pass() { + show_msg OK "$@" + part=`expr $part + $pts` + part_pos=`expr $part_pos + $pts` +} + +fail() { + show_msg WRONG "$@" + part_pos=`expr $part_pos + $pts` +} + +run_qemu() { + # Run qemu with serial output redirected to $qemu_out. If $brkfun is non-empty, + # wait until $brkfun is reached or $timeout expires, then kill QEMU + qemuextra= + if [ "$brkfun" ]; then + qemuextra="-S $qemugdb" + fi + + if [ -z "$timeout" ] || [ $timeout -le 0 ]; then + timeout=$default_timeout; + fi + + t0=$(get_time) + ( + ulimit -t $timeout + exec $qemu -nographic $qemuopts -serial file:$qemu_out -monitor null -no-reboot $qemuextra + ) > $out 2> $err & + pid=$! + + # wait for QEMU to start + sleep 1 + + if [ -n "$brkfun" ]; then + # find the address of the kernel $brkfun function + brkaddr=`$grep " $brkfun\$" $sym_table | $sed -e's/ .*$//g'` + ( + echo "target remote localhost:$gdbport" + echo "break *0x$brkaddr" + echo "continue" + ) > $gdb_in + + $gdb -batch -nx -x $gdb_in > /dev/null 2>&1 + + # make sure that QEMU is dead + # on OS X, exiting gdb doesn't always exit qemu + kill $pid > /dev/null 2>&1 + fi +} + +build_run() { + # usage: build_run + show_build_tag "$1" + shift + + if $verbose; then + echo "$make $@ ..." + fi + $make $makeopts $@ 'DEFS+=-DDEBUG_GRADE' > $out 2> $err + + if [ $? -ne 0 ]; then + echo $make $@ failed + exit 1 + fi + + # now run qemu and save the output + run_qemu + + show_time +} + +check_result() { + # usage: check_result + show_check_tag "$1" + shift + + # give qemu some time to run (for asynchronous mode) + if [ ! -s $qemu_out ]; then + sleep 4 + fi + + if [ ! -s $qemu_out ]; then + fail > /dev/null + echo 'no $qemu_out' + else + check=$1 + shift + $check "$@" + fi +} + +check_regexps() { + okay=yes + not=0 + reg=0 + error= + for i do + if [ "x$i" = "x!" ]; then + not=1 + elif [ "x$i" = "x-" ]; then + reg=1 + else + if [ $reg -ne 0 ]; then + $grep '-E' "^$i\$" $qemu_out > /dev/null + else + $grep '-F' "$i" $qemu_out > /dev/null + fi + found=$(($? == 0)) + if [ $found -eq $not ]; then + if [ $found -eq 0 ]; then + msg="!! error: missing '$i'" + else + msg="!! error: got unexpected line '$i'" + fi + okay=no + if [ -z "$error" ]; then + error="$msg" + else + error="$error\n$msg" + fi + fi + not=0 + reg=0 + fi + done + if [ "$okay" = "yes" ]; then + pass + else + fail "$error" + if $verbose; then + exit 1 + fi + fi +} + +run_test() { + # usage: run_test [-tag ] [-prog ] [-Ddef...] [-check ] checkargs ... + tag= + prog= + check=check_regexps + while true; do + select= + case $1 in + -tag|-prog) + select=`expr substr $1 2 ${#1}` + eval $select='$2' + ;; + esac + if [ -z "$select" ]; then + break + fi + shift + shift + done + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + if [ "x$1" = "x-check" ]; then + check=$2 + shift + shift + fi + + if [ -z "$prog" ]; then + $make $makeopts touch > /dev/null 2>&1 + args="$defs" + else + if [ -z "$tag" ]; then + tag="$prog" + fi + args="build-$prog $defs" + fi + + build_run "$tag" "$args" + + check_result 'check result' "$check" "$@" +} + +quick_run() { + # usage: quick_run [-Ddef...] + tag="$1" + shift + defs= + while expr "x$1" : "x-D.*" > /dev/null; do + defs="DEFS+='$1' $defs" + shift + done + + $make $makeopts touch > /dev/null 2>&1 + build_run "$tag" "$defs" +} + +quick_check() { + # usage: quick_check checkargs ... + tag="$1" + shift + check_result "$tag" check_regexps "$@" +} + +## kernel image +osimg=$(make_print ucoreimg) + +## swap image +swapimg=$(make_print swapimg) + +## set default qemu-options +qemuopts="-hda $osimg -drive file=$swapimg,media=disk,cache=writeback" + +## set break-function, default is readline +brkfun=readline + +default_check() { + pts=7 + check_regexps "$@" + + pts=3 + quick_check 'check output' \ + 'memory management: default_pmm_manager' \ + 'check_alloc_page() succeeded!' \ + 'check_pgdir() succeeded!' \ + 'check_boot_pgdir() succeeded!' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'PDE(001) fac00000-fb000000 00400000 -rw' \ + ' |-- PTE(000e0) faf00000-fafe0000 000e0000 urw' \ + ' |-- PTE(00001) fafeb000-fafec000 00001000 -rw' \ + 'check_slab() succeeded!' \ + 'check_vma_struct() succeeded!' \ + 'page fault at 0x00000100: K/W [no page found].' \ + 'check_pgfault() succeeded!' \ + 'check_vmm() succeeded.' \ + 'page fault at 0x00001000: K/W [no page found].' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'write Virt Page e in fifo_check_swap' \ + 'page fault at 0x00005000: K/W [no page found].' \ + 'page fault at 0x00001000: K/W [no page found]' \ + 'page fault at 0x00002000: K/W [no page found].' \ + 'page fault at 0x00003000: K/W [no page found].' \ + 'page fault at 0x00004000: K/W [no page found].' \ + 'check_swap() succeeded!' \ + '++ setup timer interrupts' +} + +## check now!! + +run_test -prog 'badsegment' -check default_check \ + - 'kernel_execve: pid = ., name = "badsegment".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000028' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'divzero' -check default_check \ + - 'kernel_execve: pid = ., name = "divzero".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x00000000 Divide error' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +run_test -prog 'softint' -check default_check \ + - 'kernel_execve: pid = ., name = "softint".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000d General Protection' \ + ' err 0x00000072' \ + - ' eip 0x008.....' \ + - ' esp 0xaff.....' \ + ' cs 0x----001b' \ + ' ss 0x----0023' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'faultread' -check default_check \ + - 'kernel_execve: pid = ., name = "faultread".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000004' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'faultreadkernel' -check default_check \ + - 'kernel_execve: pid = ., name = "faultreadkernel".*' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000005' \ + - ' eip 0x008.....' \ + ! - 'user panic at .*' + +run_test -prog 'hello' -check default_check \ + - 'kernel_execve: pid = ., name = "hello".*' \ + 'Hello world!!.' \ + - 'I am process .*' \ + 'hello pass.' + +run_test -prog 'testbss' -check default_check \ + - 'kernel_execve: pid = ., name = "testbss".*' \ + 'Making sure bss works right...' \ + 'Yes, good. Now doing a wild write off the end...' \ + 'testbss may pass.' \ + - 'trapframe at 0xc.......' \ + 'trap 0x0000000e Page Fault' \ + ' err 0x00000006' \ + - ' eip 0x008.....' \ + 'killed by kernel.' \ + ! - 'user panic at .*' + +run_test -prog 'pgdir' -check default_check \ + - 'kernel_execve: pid = ., name = "pgdir".*' \ + - 'I am .*' \ + 'PDE(001) 00800000-00c00000 00400000 urw' \ + ' |-- PTE(00002) 00800000-00802000 00002000 ur-' \ + ' |-- PTE(00001) 00802000-00803000 00001000 urw' \ + 'PDE(001) afc00000-b0000000 00400000 urw' \ + ' |-- PTE(00004) afffc000-b0000000 00004000 urw' \ + 'PDE(0e0) c0000000-f8000000 38000000 urw' \ + ' |-- PTE(38000) c0000000-f8000000 38000000 -rw' \ + 'pgdir pass.' + +run_test -prog 'yield' -check default_check \ + - 'kernel_execve: pid = ., name = "yield".*' \ + 'Hello, I am process 2.' \ + - 'Back in process ., iteration 0.' \ + - 'Back in process ., iteration 1.' \ + - 'Back in process ., iteration 2.' \ + - 'Back in process ., iteration 3.' \ + - 'Back in process ., iteration 4.' \ + - 'All done in process .*' \ + 'yield pass.' + + +run_test -prog 'badarg' -check default_check \ + - 'kernel_execve: pid = ., name = "badarg".*' \ + 'fork ok.' \ + 'badarg pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=10 + +run_test -prog 'exit' -check default_check \ + - 'kernel_execve: pid = ., name = "exit".*' \ + 'I am the parent. Forking the child...' \ + 'I am the parent, waiting now..' \ + 'I am the child.' \ + - 'waitpid [0-9]+ ok\.' \ + 'exit pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'spin' -check default_check \ + - 'kernel_execve: pid = ., name = "spin".*' \ + 'I am the parent. Forking the child...' \ + 'I am the parent. Running the child...' \ + 'I am the child. spinning ...' \ + 'I am the parent. Killing the child...' \ + 'kill returns 0' \ + 'wait returns 0' \ + 'spin may pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +run_test -prog 'waitkill' -check default_check \ + - 'kernel_execve: pid = ., name = "waitkill".*' \ + 'wait child 1.' \ + 'child 2.' \ + 'child 1.' \ + 'kill parent ok.' \ + 'kill child1 ok.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=15 + +run_test -prog 'forktest' -check default_check \ + - 'kernel_execve: pid = ., name = "forktest".*' \ + 'I am child 31' \ + 'I am child 19' \ + 'I am child 13' \ + 'I am child 0' \ + 'forktest pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'fork claimed to work [0-9]+ times!' \ + ! 'wait stopped early' \ + ! 'wait got too many' \ + ! - 'user panic at .*' + +pts=10 +run_test -prog 'forktree' -check default_check \ + - 'kernel_execve: pid = ., name = "forktree".*' \ + - '....: I am '\'''\' \ + - '....: I am '\''0'\' \ + - '....: I am '\'''\' \ + - '....: I am '\''1'\' \ + - '....: I am '\''0'\' \ + - '....: I am '\''01'\' \ + - '....: I am '\''00'\' \ + - '....: I am '\''11'\' \ + - '....: I am '\''10'\' \ + - '....: I am '\''101'\' \ + - '....: I am '\''100'\' \ + - '....: I am '\''111'\' \ + - '....: I am '\''110'\' \ + - '....: I am '\''001'\' \ + - '....: I am '\''000'\' \ + - '....: I am '\''011'\' \ + - '....: I am '\''010'\' \ + - '....: I am '\''0101'\' \ + - '....: I am '\''0100'\' \ + - '....: I am '\''0111'\' \ + - '....: I am '\''0110'\' \ + - '....: I am '\''0001'\' \ + - '....: I am '\''0000'\' \ + - '....: I am '\''0011'\' \ + - '....: I am '\''0010'\' \ + - '....: I am '\''1101'\' \ + - '....: I am '\''1100'\' \ + - '....: I am '\''1111'\' \ + - '....: I am '\''1110'\' \ + - '....: I am '\''1001'\' \ + - '....: I am '\''1000'\' \ + - '....: I am '\''1011'\' \ + - '....: I am '\''1010'\' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' + +pts=20 +timeout=150 +run_test -prog 'priority' -check default_check \ + 'sched class: stride_scheduler' \ + - 'kernel_execve: pid = ., name = "priority".*' \ + 'main: fork ok,now need to wait pids.' \ + 'stride sched correct result: 1 2 3 4 5' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=20 +timeout=240 + +run_test -prog 'sleep' -check default_check \ + - 'kernel_execve: pid = ., name = "sleep".*' \ + 'sleep 1 x 100 slices.' \ + 'sleep 3 x 100 slices.' \ + 'sleep 7 x 100 slices.' \ + 'sleep 10 x 100 slices.' \ + - 'use 1... msecs.' \ + 'sleep pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! ' trap 0x0000000e Page Fault' \ + ! 'killed by kernel.' \ + ! - 'user panic at .*' + +pts=20 +timeout=240 +run_test -prog 'sleepkill' -check default_check \ + - 'kernel_execve: pid = ., name = "sleepkill".*' \ + 'sleepkill pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +pts=40 +timeout=500 +run_test -prog 'matrix' -check default_check \ + 'Iter 1, No.0 philosopher_sema is thinking' \ + 'Iter 1, No.1 philosopher_sema is thinking' \ + 'Iter 1, No.2 philosopher_sema is thinking' \ + 'Iter 1, No.3 philosopher_sema is thinking' \ + 'Iter 1, No.4 philosopher_sema is thinking' \ + 'Iter 1, No.0 philosopher_sema is eating' \ + 'Iter 1, No.1 philosopher_sema is eating' \ + 'Iter 1, No.2 philosopher_sema is eating' \ + 'Iter 1, No.3 philosopher_sema is eating' \ + 'Iter 1, No.4 philosopher_sema is eating' \ + 'No.0 philosopher_sema quit' \ + 'No.1 philosopher_sema quit' \ + 'No.2 philosopher_sema quit' \ + 'No.3 philosopher_sema quit' \ + 'No.4 philosopher_sema quit' \ + 'Iter 1, No.0 philosopher_condvar is thinking' \ + 'Iter 1, No.1 philosopher_condvar is thinking' \ + 'Iter 1, No.2 philosopher_condvar is thinking' \ + 'Iter 1, No.3 philosopher_condvar is thinking' \ + 'Iter 1, No.4 philosopher_condvar is thinking' \ + 'Iter 1, No.0 philosopher_condvar is eating' \ + 'Iter 1, No.1 philosopher_condvar is eating' \ + 'Iter 1, No.2 philosopher_condvar is eating' \ + 'Iter 1, No.3 philosopher_condvar is eating' \ + 'Iter 1, No.4 philosopher_condvar is eating' \ + 'No.0 philosopher_condvar quit' \ + 'No.1 philosopher_condvar quit' \ + 'No.2 philosopher_condvar quit' \ + 'No.3 philosopher_condvar quit' \ + 'No.4 philosopher_condvar quit' \ + - 'kernel_execve: pid = ., name = "matrix".*' \ + 'fork ok.' \ + 'pid 13 done!.' \ + 'pid 17 done!.' \ + 'pid 23 done!.' \ + 'matrix pass.' \ + 'all user-mode processes have quit.' \ + 'init check memory pass.' \ + ! - 'user panic at .*' + +## print final-score +show_final + diff --git a/code/lab8/tools/kernel.ld b/code/lab8/tools/kernel.ld new file mode 100644 index 0000000..1838500 --- /dev/null +++ b/code/lab8/tools/kernel.ld @@ -0,0 +1,58 @@ +/* Simple linker script for the ucore kernel. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(kern_entry) + +SECTIONS { + /* Load the kernel at this address: "." means the current address */ + . = 0xC0100000; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Include debugging information in kernel memory */ + .stab : { + PROVIDE(__STAB_BEGIN__ = .); + *(.stab); + PROVIDE(__STAB_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + PROVIDE(__STABSTR_BEGIN__ = .); + *(.stabstr); + PROVIDE(__STABSTR_END__ = .); + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + /* The data segment */ + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack) + } +} diff --git a/code/lab8/tools/mksfs.c b/code/lab8/tools/mksfs.c new file mode 100644 index 0000000..41d2a0e --- /dev/null +++ b/code/lab8/tools/mksfs.c @@ -0,0 +1,582 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int bool; + +#define __error(msg, quit, ...) \ + do { \ + fprintf(stderr, #msg ": function %s - line %d: ", __FUNCTION__, __LINE__); \ + if (errno != 0) { \ + fprintf(stderr, "[error] %s: ", strerror(errno)); \ + } \ + fprintf(stderr, "\n\t"), fprintf(stderr, __VA_ARGS__); \ + errno = 0; \ + if (quit) { \ + exit(-1); \ + } \ + } while (0) + +#define warn(...) __error(warn, 0, __VA_ARGS__) +#define bug(...) __error(bug, 1, __VA_ARGS__) + +#define static_assert(x) \ + switch (x) {case 0: case (x): ; } + +/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */ +#define GOLDEN_RATIO_PRIME_32 0x9e370001UL + +#define HASH_SHIFT 10 +#define HASH_LIST_SIZE (1 << HASH_SHIFT) + +static inline uint32_t +__hash32(uint32_t val, unsigned int bits) { + uint32_t hash = val * GOLDEN_RATIO_PRIME_32; + return (hash >> (32 - bits)); +} + +static uint32_t +hash32(uint32_t val) { + return __hash32(val, HASH_SHIFT); +} + +static uint32_t +hash64(uint64_t val) { + return __hash32((uint32_t)val, HASH_SHIFT); +} + +void * +safe_malloc(size_t size) { + void *ret; + if ((ret = malloc(size)) == NULL) { + bug("malloc %lu bytes failed.\n", (long unsigned)size); + } + return ret; +} + +char * +safe_strdup(const char *str) { + char *ret; + if ((ret = strdup(str)) == NULL) { + bug("strdup failed: %s\n", str); + } + return ret; +} + +struct stat * +safe_stat(const char *filename) { + static struct stat __stat; + if (stat(filename, &__stat) != 0) { + bug("stat %s failed.\n", filename); + } + return &__stat; +} + +struct stat * +safe_fstat(int fd) { + static struct stat __stat; + if (fstat(fd, &__stat) != 0) { + bug("fstat %d failed.\n", fd); + } + return &__stat; +} + +struct stat * +safe_lstat(const char *name) { + static struct stat __stat; + if (lstat(name, &__stat) != 0) { + bug("lstat '%s' failed.\n", name); + } + return &__stat; +} + +void +safe_fchdir(int fd) { + if (fchdir(fd) != 0) { + bug("fchdir failed %d.\n", fd); + } +} + +#define SFS_MAGIC 0x2f8dbe2a +#define SFS_NDIRECT 12 +#define SFS_BLKSIZE 4096 // 4K +#define SFS_MAX_NBLKS (1024UL * 512) // 4K * 512K +#define SFS_MAX_INFO_LEN 31 +#define SFS_MAX_FNAME_LEN 255 +#define SFS_MAX_FILE_SIZE (1024UL * 1024 * 128) // 128M + +#define SFS_BLKBITS (SFS_BLKSIZE * CHAR_BIT) +#define SFS_TYPE_FILE 1 +#define SFS_TYPE_DIR 2 +#define SFS_TYPE_LINK 3 + +#define SFS_BLKN_SUPER 0 +#define SFS_BLKN_ROOT 1 +#define SFS_BLKN_FREEMAP 2 + +struct cache_block { + uint32_t ino; + struct cache_block *hash_next; + void *cache; +}; + +struct cache_inode { + struct inode { + uint32_t size; + uint16_t type; + uint16_t nlinks; + uint32_t blocks; + uint32_t direct[SFS_NDIRECT]; + uint32_t indirect; + uint32_t db_indirect; + } inode; + ino_t real; + uint32_t ino; + uint32_t nblks; + struct cache_block *l1, *l2; + struct cache_inode *hash_next; +}; + +struct sfs_fs { + struct { + uint32_t magic; + uint32_t blocks; + uint32_t unused_blocks; + char info[SFS_MAX_INFO_LEN + 1]; + } super; + struct subpath { + struct subpath *next, *prev; + char *subname; + } __sp_nil, *sp_root, *sp_end; + int imgfd; + uint32_t ninos, next_ino; + struct cache_inode *root; + struct cache_inode *inodes[HASH_LIST_SIZE]; + struct cache_block *blocks[HASH_LIST_SIZE]; +}; + +struct sfs_entry { + uint32_t ino; + char name[SFS_MAX_FNAME_LEN + 1]; +}; + +static uint32_t +sfs_alloc_ino(struct sfs_fs *sfs) { + if (sfs->next_ino < sfs->ninos) { + sfs->super.unused_blocks --; + return sfs->next_ino ++; + } + bug("out of disk space.\n"); +} + +static struct cache_block * +alloc_cache_block(struct sfs_fs *sfs, uint32_t ino) { + struct cache_block *cb = safe_malloc(sizeof(struct cache_block)); + cb->ino = (ino != 0) ? ino : sfs_alloc_ino(sfs); + cb->cache = memset(safe_malloc(SFS_BLKSIZE), 0, SFS_BLKSIZE); + struct cache_block **head = sfs->blocks + hash32(ino); + cb->hash_next = *head, *head = cb; + return cb; +} + +struct cache_block * +search_cache_block(struct sfs_fs *sfs, uint32_t ino) { + struct cache_block *cb = sfs->blocks[hash32(ino)]; + while (cb != NULL && cb->ino != ino) { + cb = cb->hash_next; + } + return cb; +} + +static struct cache_inode * +alloc_cache_inode(struct sfs_fs *sfs, ino_t real, uint32_t ino, uint16_t type) { + struct cache_inode *ci = safe_malloc(sizeof(struct cache_inode)); + ci->ino = (ino != 0) ? ino : sfs_alloc_ino(sfs); + ci->real = real, ci->nblks = 0, ci->l1 = ci->l2 = NULL; + struct inode *inode = &(ci->inode); + memset(inode, 0, sizeof(struct inode)); + inode->type = type; + struct cache_inode **head = sfs->inodes + hash64(real); + ci->hash_next = *head, *head = ci; + return ci; +} + +struct cache_inode * +search_cache_inode(struct sfs_fs *sfs, ino_t real) { + struct cache_inode *ci = sfs->inodes[hash64(real)]; + while (ci != NULL && ci->real != real) { + ci = ci->hash_next; + } + return ci; +} + +struct sfs_fs * +create_sfs(int imgfd) { + uint32_t ninos, next_ino; + struct stat *stat = safe_fstat(imgfd); + if ((ninos = stat->st_size / SFS_BLKSIZE) > SFS_MAX_NBLKS) { + ninos = SFS_MAX_NBLKS; + warn("img file is too big (%llu bytes, only use %u blocks).\n", + (unsigned long long)stat->st_size, ninos); + } + if ((next_ino = SFS_BLKN_FREEMAP + (ninos + SFS_BLKBITS - 1) / SFS_BLKBITS) >= ninos) { + bug("img file is too small (%llu bytes, %u blocks, bitmap use at least %u blocks).\n", + (unsigned long long)stat->st_size, ninos, next_ino - 2); + } + + struct sfs_fs *sfs = safe_malloc(sizeof(struct sfs_fs)); + sfs->super.magic = SFS_MAGIC; + sfs->super.blocks = ninos, sfs->super.unused_blocks = ninos - next_ino; + snprintf(sfs->super.info, SFS_MAX_INFO_LEN, "simple file system"); + + sfs->ninos = ninos, sfs->next_ino = next_ino, sfs->imgfd = imgfd; + sfs->sp_root = sfs->sp_end = &(sfs->__sp_nil); + sfs->sp_end->prev = sfs->sp_end->next = NULL; + + int i; + for (i = 0; i < HASH_LIST_SIZE; i ++) { + sfs->inodes[i] = NULL; + sfs->blocks[i] = NULL; + } + + sfs->root = alloc_cache_inode(sfs, 0, SFS_BLKN_ROOT, SFS_TYPE_DIR); + return sfs; +} + +static void +subpath_push(struct sfs_fs *sfs, const char *subname) { + struct subpath *subpath = safe_malloc(sizeof(struct subpath)); + subpath->subname = safe_strdup(subname); + sfs->sp_end->next = subpath; + subpath->prev = sfs->sp_end; + subpath->next = NULL; + sfs->sp_end = subpath; +} + +static void +subpath_pop(struct sfs_fs *sfs) { + assert(sfs->sp_root != sfs->sp_end); + struct subpath *subpath = sfs->sp_end; + sfs->sp_end = sfs->sp_end->prev, sfs->sp_end->next = NULL; + free(subpath->subname), free(subpath); +} + +static void +subpath_show(FILE *fout, struct sfs_fs *sfs, const char *name) { + struct subpath *subpath = sfs->sp_root; + fprintf(fout, "current is: /"); + while ((subpath = subpath->next) != NULL) { + fprintf(fout, "%s/", subpath->subname); + } + if (name != NULL) { + fprintf(fout, "%s", name); + } + fprintf(fout, "\n"); +} + +static void +write_block(struct sfs_fs *sfs, void *data, size_t len, uint32_t ino) { + assert(len <= SFS_BLKSIZE && ino < sfs->ninos); + static char buffer[SFS_BLKSIZE]; + if (len != SFS_BLKSIZE) { + memset(buffer, 0, sizeof(buffer)); + data = memcpy(buffer, data, len); + } + off_t offset = (off_t)ino * SFS_BLKSIZE; + ssize_t ret; + if ((ret = pwrite(sfs->imgfd, data, SFS_BLKSIZE, offset)) != SFS_BLKSIZE) { + bug("write %u block failed: (%d/%d).\n", ino, (int)ret, SFS_BLKSIZE); + } +} + +static void +flush_cache_block(struct sfs_fs *sfs, struct cache_block *cb) { + write_block(sfs, cb->cache, SFS_BLKSIZE, cb->ino); +} + +static void +flush_cache_inode(struct sfs_fs *sfs, struct cache_inode *ci) { + write_block(sfs, &(ci->inode), sizeof(ci->inode), ci->ino); +} + +void +close_sfs(struct sfs_fs *sfs) { + static char buffer[SFS_BLKSIZE]; + uint32_t i, j, ino = SFS_BLKN_FREEMAP; + uint32_t ninos = sfs->ninos, next_ino = sfs->next_ino; + for (i = 0; i < ninos; ino ++, i += SFS_BLKBITS) { + memset(buffer, 0, sizeof(buffer)); + if (i + SFS_BLKBITS > next_ino) { + uint32_t start = 0, end = SFS_BLKBITS; + if (i < next_ino) { + start = next_ino - i; + } + if (i + SFS_BLKBITS > ninos) { + end = ninos - i; + } + uint32_t *data = (uint32_t *)buffer; + const uint32_t bits = sizeof(bits) * CHAR_BIT; + for (j = start; j < end; j ++) { + data[j / bits] |= (1 << (j % bits)); + } + } + write_block(sfs, buffer, sizeof(buffer), ino); + } + write_block(sfs, &(sfs->super), sizeof(sfs->super), SFS_BLKN_SUPER); + + for (i = 0; i < HASH_LIST_SIZE; i ++) { + struct cache_block *cb = sfs->blocks[i]; + while (cb != NULL) { + flush_cache_block(sfs, cb); + cb = cb->hash_next; + } + struct cache_inode *ci = sfs->inodes[i]; + while (ci != NULL) { + flush_cache_inode(sfs, ci); + ci = ci->hash_next; + } + } +} + +struct sfs_fs * +open_img(const char *imgname) { + const char *expect = ".img", *ext = imgname + strlen(imgname) - strlen(expect); + if (ext <= imgname || strcmp(ext, expect) != 0) { + bug("invalid .img file name '%s'.\n", imgname); + } + int imgfd; + if ((imgfd = open(imgname, O_WRONLY)) < 0) { + bug("open '%s' failed.\n", imgname); + } + return create_sfs(imgfd); +} + +#define open_bug(sfs, name, ...) \ + do { \ + subpath_show(stderr, sfs, name); \ + bug(__VA_ARGS__); \ + } while (0) + +#define show_fullpath(sfs, name) subpath_show(stderr, sfs, name) + +void open_dir(struct sfs_fs *sfs, struct cache_inode *current, struct cache_inode *parent); +void open_file(struct sfs_fs *sfs, struct cache_inode *file, const char *filename, int fd); +void open_link(struct sfs_fs *sfs, struct cache_inode *file, const char *filename); + +#define SFS_BLK_NENTRY (SFS_BLKSIZE / sizeof(uint32_t)) +#define SFS_L0_NBLKS SFS_NDIRECT +#define SFS_L1_NBLKS (SFS_BLK_NENTRY + SFS_L0_NBLKS) +#define SFS_L2_NBLKS (SFS_BLK_NENTRY * SFS_BLK_NENTRY + SFS_L1_NBLKS) +#define SFS_LN_NBLKS (SFS_MAX_FILE_SIZE / SFS_BLKSIZE) + +static void +update_cache(struct sfs_fs *sfs, struct cache_block **cbp, uint32_t *inop) { + uint32_t ino = *inop; + struct cache_block *cb = *cbp; + if (ino == 0) { + cb = alloc_cache_block(sfs, 0); + ino = cb->ino; + } + else if (cb == NULL || cb->ino != ino) { + cb = search_cache_block(sfs, ino); + assert(cb != NULL && cb->ino == ino); + } + *cbp = cb, *inop = ino; +} + +static void +append_block(struct sfs_fs *sfs, struct cache_inode *file, size_t size, uint32_t ino, const char *filename) { + static_assert(SFS_LN_NBLKS <= SFS_L2_NBLKS); + assert(size <= SFS_BLKSIZE); + uint32_t nblks = file->nblks; + struct inode *inode = &(file->inode); + if (nblks >= SFS_LN_NBLKS) { + open_bug(sfs, filename, "file is too big.\n"); + } + if (nblks < SFS_L0_NBLKS) { + inode->direct[nblks] = ino; + } + else if (nblks < SFS_L1_NBLKS) { + nblks -= SFS_L0_NBLKS; + update_cache(sfs, &(file->l1), &(inode->indirect)); + uint32_t *data = file->l1->cache; + data[nblks] = ino; + } + else if (nblks < SFS_L2_NBLKS) { + nblks -= SFS_L1_NBLKS; + update_cache(sfs, &(file->l2), &(inode->db_indirect)); + uint32_t *data2 = file->l2->cache; + update_cache(sfs, &(file->l1), &data2[nblks / SFS_BLK_NENTRY]); + uint32_t *data1 = file->l1->cache; + data1[nblks % SFS_BLK_NENTRY] = ino; + } + file->nblks ++; + inode->size += size; + inode->blocks ++; +} + +static void +add_entry(struct sfs_fs *sfs, struct cache_inode *current, struct cache_inode *file, const char *name) { + static struct sfs_entry __entry, *entry = &__entry; + assert(current->inode.type == SFS_TYPE_DIR && strlen(name) <= SFS_MAX_FNAME_LEN); + entry->ino = file->ino, strcpy(entry->name, name); + uint32_t entry_ino = sfs_alloc_ino(sfs); + write_block(sfs, entry, sizeof(entry->name), entry_ino); + append_block(sfs, current, sizeof(entry->name), entry_ino, name); + file->inode.nlinks ++; +} + +static void +add_dir(struct sfs_fs *sfs, struct cache_inode *parent, const char *dirname, int curfd, int fd, ino_t real) { + assert(search_cache_inode(sfs, real) == NULL); + struct cache_inode *current = alloc_cache_inode(sfs, real, 0, SFS_TYPE_DIR); + safe_fchdir(fd), subpath_push(sfs, dirname); + open_dir(sfs, current, parent); + safe_fchdir(curfd), subpath_pop(sfs); + add_entry(sfs, parent, current, dirname); +} + +static void +add_file(struct sfs_fs *sfs, struct cache_inode *current, const char *filename, int fd, ino_t real) { + struct cache_inode *file; + if ((file = search_cache_inode(sfs, real)) == NULL) { + file = alloc_cache_inode(sfs, real, 0, SFS_TYPE_FILE); + open_file(sfs, file, filename, fd); + } + add_entry(sfs, current, file, filename); +} + +static void +add_link(struct sfs_fs *sfs, struct cache_inode *current, const char *filename, ino_t real) { + struct cache_inode *file = alloc_cache_inode(sfs, real, 0, SFS_TYPE_LINK); + open_link(sfs, file, filename); + add_entry(sfs, current, file, filename); +} + +void +open_dir(struct sfs_fs *sfs, struct cache_inode *current, struct cache_inode *parent) { + DIR *dir; + if ((dir = opendir(".")) == NULL) { + open_bug(sfs, NULL, "opendir failed.\n"); + } + add_entry(sfs, current, current, "."); + add_entry(sfs, current, parent, ".."); + struct dirent *direntp; + while ((direntp = readdir(dir)) != NULL) { + const char *name = direntp->d_name; + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + continue ; + } + if (name[0] == '.') { + continue ; + } + if (strlen(name) > SFS_MAX_FNAME_LEN) { + open_bug(sfs, NULL, "file name is too long: %s\n", name); + } + struct stat *stat = safe_lstat(name); + if (S_ISLNK(stat->st_mode)) { + add_link(sfs, current, name, stat->st_ino); + } + else { + int fd; + if ((fd = open(name, O_RDONLY)) < 0) { + open_bug(sfs, NULL, "open failed: %s\n", name); + } + if (S_ISDIR(stat->st_mode)) { + add_dir(sfs, current, name, dirfd(dir), fd, stat->st_ino); + } + else if (S_ISREG(stat->st_mode)) { + add_file(sfs, current, name, fd, stat->st_ino); + } + else { + char mode = '?'; + if (S_ISFIFO(stat->st_mode)) mode = 'f'; + if (S_ISSOCK(stat->st_mode)) mode = 's'; + if (S_ISCHR(stat->st_mode)) mode = 'c'; + if (S_ISBLK(stat->st_mode)) mode = 'b'; + show_fullpath(sfs, NULL); + warn("unsupported mode %07x (%c): file %s\n", stat->st_mode, mode, name); + } + close(fd); + } + } + closedir(dir); +} + +void +open_file(struct sfs_fs *sfs, struct cache_inode *file, const char *filename, int fd) { + static char buffer[SFS_BLKSIZE]; + ssize_t ret, last = SFS_BLKSIZE; + while ((ret = read(fd, buffer, sizeof(buffer))) != 0) { + assert(last == SFS_BLKSIZE); + uint32_t ino = sfs_alloc_ino(sfs); + write_block(sfs, buffer, ret, ino); + append_block(sfs, file, ret, ino, filename); + last = ret; + } + if (ret < 0) { + open_bug(sfs, filename, "read file failed.\n"); + } +} + +void +open_link(struct sfs_fs *sfs, struct cache_inode *file, const char *filename) { + static char buffer[SFS_BLKSIZE]; + uint32_t ino = sfs_alloc_ino(sfs); + ssize_t ret = readlink(filename, buffer, sizeof(buffer)); + if (ret < 0 || ret == SFS_BLKSIZE) { + open_bug(sfs, filename, "read link failed, %d", (int)ret); + } + write_block(sfs, buffer, ret, ino); + append_block(sfs, file, ret, ino, filename); +} + +int +create_img(struct sfs_fs *sfs, const char *home) { + int curfd, homefd; + if ((curfd = open(".", O_RDONLY)) < 0) { + bug("get current fd failed.\n"); + } + if ((homefd = open(home, O_RDONLY | O_NOFOLLOW)) < 0) { + bug("open home directory '%s' failed.\n", home); + } + safe_fchdir(homefd); + open_dir(sfs, sfs->root, sfs->root); + safe_fchdir(curfd); + close(curfd), close(homefd); + close_sfs(sfs); + return 0; +} + +static void +static_check(void) { + static_assert(sizeof(off_t) == 8); + static_assert(sizeof(ino_t) == 8); + static_assert(SFS_MAX_NBLKS <= 0x80000000UL); + static_assert(SFS_MAX_FILE_SIZE <= 0x80000000UL); +} + +int +main(int argc, char **argv) { + static_check(); + if (argc != 3) { + bug("usage: \n"); + } + const char *imgname = argv[1], *home = argv[2]; + if (create_img(open_img(imgname), home) != 0) { + bug("create img failed.\n"); + } + printf("create %s (%s) successfully.\n", imgname, home); + return 0; +} + diff --git a/code/lab8/tools/sign.c b/code/lab8/tools/sign.c new file mode 100644 index 0000000..9d81bb6 --- /dev/null +++ b/code/lab8/tools/sign.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) { + struct stat st; + if (argc != 3) { + fprintf(stderr, "Usage: \n"); + return -1; + } + if (stat(argv[1], &st) != 0) { + fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno)); + return -1; + } + printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size); + if (st.st_size > 510) { + fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size); + return -1; + } + char buf[512]; + memset(buf, 0, sizeof(buf)); + FILE *ifp = fopen(argv[1], "rb"); + int size = fread(buf, 1, st.st_size, ifp); + if (size != st.st_size) { + fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size); + return -1; + } + fclose(ifp); + buf[510] = 0x55; + buf[511] = 0xAA; + FILE *ofp = fopen(argv[2], "wb+"); + size = fwrite(buf, 1, 512, ofp); + if (size != 512) { + fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size); + return -1; + } + fclose(ofp); + printf("build 512 bytes boot sector: '%s' success!\n", argv[2]); + return 0; +} + diff --git a/code/lab8/tools/user.ld b/code/lab8/tools/user.ld new file mode 100644 index 0000000..c73b6fa --- /dev/null +++ b/code/lab8/tools/user.ld @@ -0,0 +1,71 @@ +/* Simple linker script for ucore user-level programs. + See the GNU ld 'info' manual ("info ld") to learn the syntax. */ + +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) + +SECTIONS { + /* Load programs at this address: "." means the current address */ + . = 0x800020; + + .text : { + *(.text .stub .text.* .gnu.linkonce.t.*) + } + + PROVIDE(etext = .); /* Define the 'etext' symbol to this value */ + + .rodata : { + *(.rodata .rodata.* .gnu.linkonce.r.*) + } + + /* Adjust the address for the data segment to the next page */ + . = ALIGN(0x1000); + + .data : { + *(.data) + } + + PROVIDE(edata = .); + + .bss : { + *(.bss) + } + + PROVIDE(end = .); + + + /* Place debugging symbols so that they can be found by + * the kernel debugger. + * Specifically, the four words at 0x200000 mark the beginning of + * the stabs, the end of the stabs, the beginning of the stabs + * string table, and the end of the stabs string table, respectively. + */ + + .stab_info 0x200000 : { + LONG(__STAB_BEGIN__); + LONG(__STAB_END__); + LONG(__STABSTR_BEGIN__); + LONG(__STABSTR_END__); + } + + .stab : { + __STAB_BEGIN__ = DEFINED(__STAB_BEGIN__) ? __STAB_BEGIN__ : .; + *(.stab); + __STAB_END__ = DEFINED(__STAB_END__) ? __STAB_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + .stabstr : { + __STABSTR_BEGIN__ = DEFINED(__STABSTR_BEGIN__) ? __STABSTR_BEGIN__ : .; + *(.stabstr); + __STABSTR_END__ = DEFINED(__STABSTR_END__) ? __STABSTR_END__ : .; + BYTE(0) /* Force the linker to allocate space + for this section */ + } + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack .comment) + } +} diff --git a/code/lab8/tools/vector.c b/code/lab8/tools/vector.c new file mode 100644 index 0000000..e24d77e --- /dev/null +++ b/code/lab8/tools/vector.c @@ -0,0 +1,29 @@ +#include + +int +main(void) { + printf("# handler\n"); + printf(".text\n"); + printf(".globl __alltraps\n"); + + int i; + for (i = 0; i < 256; i ++) { + printf(".globl vector%d\n", i); + printf("vector%d:\n", i); + if ((i < 8 || i > 14) && i != 17) { + printf(" pushl $0\n"); + } + printf(" pushl $%d\n", i); + printf(" jmp __alltraps\n"); + } + printf("\n"); + printf("# vector table\n"); + printf(".data\n"); + printf(".globl __vectors\n"); + printf("__vectors:\n"); + for (i = 0; i < 256; i ++) { + printf(" .long vector%d\n", i); + } + return 0; +} + diff --git a/code/lab8/user/badarg.c b/code/lab8/user/badarg.c new file mode 100644 index 0000000..7b4ffad --- /dev/null +++ b/code/lab8/user/badarg.c @@ -0,0 +1,22 @@ +#include +#include + +int +main(void) { + int pid, exit_code; + if ((pid = fork()) == 0) { + cprintf("fork ok.\n"); + int i; + for (i = 0; i < 10; i ++) { + yield(); + } + exit(0xbeaf); + } + assert(pid > 0); + assert(waitpid(-1, NULL) != 0); + assert(waitpid(pid, (void *)0xC0000000) != 0); + assert(waitpid(pid, &exit_code) == 0 && exit_code == 0xbeaf); + cprintf("badarg pass.\n"); + return 0; +} + diff --git a/code/lab8/user/badsegment.c b/code/lab8/user/badsegment.c new file mode 100644 index 0000000..cdd9e99 --- /dev/null +++ b/code/lab8/user/badsegment.c @@ -0,0 +1,11 @@ +#include +#include + +/* try to load the kernel's TSS selector into the DS register */ + +int +main(void) { + asm volatile("movw $0x28,%ax; movw %ax,%ds"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/divzero.c b/code/lab8/user/divzero.c new file mode 100644 index 0000000..16c23d5 --- /dev/null +++ b/code/lab8/user/divzero.c @@ -0,0 +1,11 @@ +#include +#include + +int zero; + +int +main(void) { + cprintf("value is %d.\n", 1 / zero); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/exit.c b/code/lab8/user/exit.c new file mode 100644 index 0000000..c3ac5f8 --- /dev/null +++ b/code/lab8/user/exit.c @@ -0,0 +1,34 @@ +#include +#include + +int magic = -0x10384; + +int +main(void) { + int pid, code; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child.\n"); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); + exit(magic); + } + else { + cprintf("I am parent, fork a child pid %d\n",pid); + } + assert(pid > 0); + cprintf("I am the parent, waiting now..\n"); + + assert(waitpid(pid, &code) == 0 && code == magic); + assert(waitpid(pid, &code) != 0 && wait() != 0); + cprintf("waitpid %d ok.\n", pid); + + cprintf("exit pass.\n"); + return 0; +} + diff --git a/code/lab8/user/faultread.c b/code/lab8/user/faultread.c new file mode 100644 index 0000000..6d292e1 --- /dev/null +++ b/code/lab8/user/faultread.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %8x from 0.\n", *(unsigned int *)0); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/faultreadkernel.c b/code/lab8/user/faultreadkernel.c new file mode 100644 index 0000000..53457f6 --- /dev/null +++ b/code/lab8/user/faultreadkernel.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + cprintf("I read %08x from 0xfac00000!\n", *(unsigned *)0xfac00000); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/forktest.c b/code/lab8/user/forktest.c new file mode 100644 index 0000000..3eda228 --- /dev/null +++ b/code/lab8/user/forktest.c @@ -0,0 +1,34 @@ +#include +#include + +const int max_child = 32; + +int +main(void) { + int n, pid; + for (n = 0; n < max_child; n ++) { + if ((pid = fork()) == 0) { + cprintf("I am child %d\n", n); + exit(0); + } + assert(pid > 0); + } + + if (n > max_child) { + panic("fork claimed to work %d times!\n", n); + } + + for (; n > 0; n --) { + if (wait() != 0) { + panic("wait stopped early\n"); + } + } + + if (wait() == 0) { + panic("wait got too many\n"); + } + + cprintf("forktest pass.\n"); + return 0; +} + diff --git a/code/lab8/user/forktree.c b/code/lab8/user/forktree.c new file mode 100644 index 0000000..93f28bb --- /dev/null +++ b/code/lab8/user/forktree.c @@ -0,0 +1,37 @@ +#include +#include +#include + +#define DEPTH 4 + +void forktree(const char *cur); + +void +forkchild(const char *cur, char branch) { + char nxt[DEPTH + 1]; + + if (strlen(cur) >= DEPTH) + return; + + snprintf(nxt, DEPTH + 1, "%s%c", cur, branch); + if (fork() == 0) { + forktree(nxt); + yield(); + exit(0); + } +} + +void +forktree(const char *cur) { + cprintf("%04x: I am '%s'\n", getpid(), cur); + + forkchild(cur, '0'); + forkchild(cur, '1'); +} + +int +main(void) { + forktree(""); + return 0; +} + diff --git a/code/lab8/user/hello.c b/code/lab8/user/hello.c new file mode 100644 index 0000000..0f05251 --- /dev/null +++ b/code/lab8/user/hello.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("Hello world!!.\n"); + cprintf("I am process %d.\n", getpid()); + cprintf("hello pass.\n"); + return 0; +} + diff --git a/code/lab8/user/libs/dir.c b/code/lab8/user/libs/dir.c new file mode 100644 index 0000000..62d926e --- /dev/null +++ b/code/lab8/user/libs/dir.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DIR dir, *dirp=&dir; +DIR * +opendir(const char *path) { + + if ((dirp->fd = open(path, O_RDONLY)) < 0) { + goto failed; + } + struct stat __stat, *stat = &__stat; + if (fstat(dirp->fd, stat) != 0 || !S_ISDIR(stat->st_mode)) { + goto failed; + } + dirp->dirent.offset = 0; + return dirp; + +failed: + return NULL; +} + +struct dirent * +readdir(DIR *dirp) { + if (sys_getdirentry(dirp->fd, &(dirp->dirent)) == 0) { + return &(dirp->dirent); + } + return NULL; +} + +void +closedir(DIR *dirp) { + close(dirp->fd); +} + +int +getcwd(char *buffer, size_t len) { + return sys_getcwd(buffer, len); +} + diff --git a/code/lab8/user/libs/dir.h b/code/lab8/user/libs/dir.h new file mode 100644 index 0000000..4862492 --- /dev/null +++ b/code/lab8/user/libs/dir.h @@ -0,0 +1,19 @@ +#ifndef __USER_LIBS_DIR_H__ +#define __USER_LIBS_DIR_H__ + +#include +#include + +typedef struct { + int fd; + struct dirent dirent; +} DIR; + +DIR *opendir(const char *path); +struct dirent *readdir(DIR *dirp); +void closedir(DIR *dirp); +int chdir(const char *path); +int getcwd(char *buffer, size_t len); + +#endif /* !__USER_LIBS_DIR_H__ */ + diff --git a/code/lab8/user/libs/file.c b/code/lab8/user/libs/file.c new file mode 100644 index 0000000..113e078 --- /dev/null +++ b/code/lab8/user/libs/file.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include + +int +open(const char *path, uint32_t open_flags) { + return sys_open(path, open_flags); +} + +int +close(int fd) { + return sys_close(fd); +} + +int +read(int fd, void *base, size_t len) { + return sys_read(fd, base, len); +} + +int +write(int fd, void *base, size_t len) { + return sys_write(fd, base, len); +} + +int +seek(int fd, off_t pos, int whence) { + return sys_seek(fd, pos, whence); +} + +int +fstat(int fd, struct stat *stat) { + return sys_fstat(fd, stat); +} + +int +fsync(int fd) { + return sys_fsync(fd); +} + +int +dup2(int fd1, int fd2) { + return sys_dup(fd1, fd2); +} + +static char +transmode(struct stat *stat) { + uint32_t mode = stat->st_mode; + if (S_ISREG(mode)) return 'r'; + if (S_ISDIR(mode)) return 'd'; + if (S_ISLNK(mode)) return 'l'; + if (S_ISCHR(mode)) return 'c'; + if (S_ISBLK(mode)) return 'b'; + return '-'; +} + +void +print_stat(const char *name, int fd, struct stat *stat) { + cprintf("[%03d] %s\n", fd, name); + cprintf(" mode : %c\n", transmode(stat)); + cprintf(" links : %lu\n", stat->st_nlinks); + cprintf(" blocks : %lu\n", stat->st_blocks); + cprintf(" size : %lu\n", stat->st_size); +} + diff --git a/code/lab8/user/libs/file.h b/code/lab8/user/libs/file.h new file mode 100644 index 0000000..fc46fb5 --- /dev/null +++ b/code/lab8/user/libs/file.h @@ -0,0 +1,23 @@ +#ifndef __USER_LIBS_FILE_H__ +#define __USER_LIBS_FILE_H__ + +#include + +struct stat; + +int open(const char *path, uint32_t open_flags); +int close(int fd); +int read(int fd, void *base, size_t len); +int write(int fd, void *base, size_t len); +int seek(int fd, off_t pos, int whence); +int fstat(int fd, struct stat *stat); +int fsync(int fd); +int dup(int fd); +int dup2(int fd1, int fd2); +int pipe(int *fd_store); +int mkfifo(const char *name, uint32_t open_flags); + +void print_stat(const char *name, int fd, struct stat *stat); + +#endif /* !__USER_LIBS_FILE_H__ */ + diff --git a/code/lab8/user/libs/initcode.S b/code/lab8/user/libs/initcode.S new file mode 100644 index 0000000..e6b4936 --- /dev/null +++ b/code/lab8/user/libs/initcode.S @@ -0,0 +1,24 @@ +.text +.globl _start +_start: + # set ebp for backtrace + movl $0x0, %ebp + + # load argc and argv + movl (%esp), %ebx + lea 0x4(%esp), %ecx + + + # move down the esp register + # since it may cause page fault in backtrace + subl $0x20, %esp + + # save argc and argv on stack + pushl %ecx + pushl %ebx + + # call user-program function + call umain +1: jmp 1b + + diff --git a/code/lab8/user/libs/lock.h b/code/lab8/user/libs/lock.h new file mode 100644 index 0000000..e221c12 --- /dev/null +++ b/code/lab8/user/libs/lock.h @@ -0,0 +1,42 @@ +#ifndef __USER_LIBS_LOCK_H__ +#define __USER_LIBS_LOCK_H__ + +#include +#include +#include + +#define INIT_LOCK {0} + +typedef volatile bool lock_t; + +static inline void +lock_init(lock_t *l) { + *l = 0; +} + +static inline bool +try_lock(lock_t *l) { + return test_and_set_bit(0, l); +} + +static inline void +lock(lock_t *l) { + if (try_lock(l)) { + int step = 0; + do { + yield(); + if (++ step == 100) { + step = 0; + sleep(10); + } + } while (try_lock(l)); + } +} + +static inline void +unlock(lock_t *l) { + test_and_clear_bit(0, l); +} + +#endif /* !__USER_LIBS_LOCK_H__ */ + diff --git a/code/lab8/user/libs/panic.c b/code/lab8/user/libs/panic.c new file mode 100644 index 0000000..783be16 --- /dev/null +++ b/code/lab8/user/libs/panic.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +void +__panic(const char *file, int line, const char *fmt, ...) { + // print the 'message' + va_list ap; + va_start(ap, fmt); + cprintf("user panic at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); + exit(-E_PANIC); +} + +void +__warn(const char *file, int line, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + cprintf("user warning at %s:%d:\n ", file, line); + vcprintf(fmt, ap); + cprintf("\n"); + va_end(ap); +} + diff --git a/code/lab8/user/libs/stdio.c b/code/lab8/user/libs/stdio.c new file mode 100644 index 0000000..56c5499 --- /dev/null +++ b/code/lab8/user/libs/stdio.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include + +/* * + * cputch - writes a single character @c to stdout, and it will + * increace the value of counter pointed by @cnt. + * */ +static void +cputch(int c, int *cnt) { + sys_putc(c); + (*cnt) ++; +} + +/* * + * vcprintf - format a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * + * Call this function if you are already dealing with a va_list. + * Or you probably want cprintf() instead. + * */ +int +vcprintf(const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)cputch, NO_FD, &cnt, fmt, ap); + return cnt; +} + +/* * + * cprintf - formats a string and writes it to stdout + * + * The return value is the number of characters which would be + * written to stdout. + * */ +int +cprintf(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + int cnt = vcprintf(fmt, ap); + va_end(ap); + + return cnt; +} + +/* * + * cputs- writes the string pointed by @str to stdout and + * appends a newline character. + * */ +int +cputs(const char *str) { + int cnt = 0; + char c; + while ((c = *str ++) != '\0') { + cputch(c, &cnt); + } + cputch('\n', &cnt); + return cnt; +} + + +static void +fputch(char c, int *cnt, int fd) { + write(fd, &c, sizeof(char)); + (*cnt) ++; +} + +int +vfprintf(int fd, const char *fmt, va_list ap) { + int cnt = 0; + vprintfmt((void*)fputch, fd, &cnt, fmt, ap); + return cnt; +} + +int +fprintf(int fd, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + int cnt = vfprintf(fd, fmt, ap); + va_end(ap); + + return cnt; +} diff --git a/code/lab8/user/libs/syscall.c b/code/lab8/user/libs/syscall.c new file mode 100644 index 0000000..d6ae096 --- /dev/null +++ b/code/lab8/user/libs/syscall.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include + + +#define MAX_ARGS 5 + +static inline int +syscall(int num, ...) { + va_list ap; + va_start(ap, num); + uint32_t a[MAX_ARGS]; + int i, ret; + for (i = 0; i < MAX_ARGS; i ++) { + a[i] = va_arg(ap, uint32_t); + } + va_end(ap); + + asm volatile ( + "int %1;" + : "=a" (ret) + : "i" (T_SYSCALL), + "a" (num), + "d" (a[0]), + "c" (a[1]), + "b" (a[2]), + "D" (a[3]), + "S" (a[4]) + : "cc", "memory"); + return ret; +} + +int +sys_exit(int error_code) { + return syscall(SYS_exit, error_code); +} + +int +sys_fork(void) { + return syscall(SYS_fork); +} + +int +sys_wait(int pid, int *store) { + return syscall(SYS_wait, pid, store); +} + +int +sys_yield(void) { + return syscall(SYS_yield); +} + +int +sys_kill(int pid) { + return syscall(SYS_kill, pid); +} + +int +sys_getpid(void) { + return syscall(SYS_getpid); +} + +int +sys_putc(int c) { + return syscall(SYS_putc, c); +} + +int +sys_pgdir(void) { + return syscall(SYS_pgdir); +} + +void +sys_lab6_set_priority(uint32_t priority) +{ + syscall(SYS_lab6_set_priority, priority); +} + +int +sys_sleep(unsigned int time) { + return syscall(SYS_sleep, time); +} + +size_t +sys_gettime(void) { + return syscall(SYS_gettime); +} + +int +sys_exec(const char *name, int argc, const char **argv) { + return syscall(SYS_exec, name, argc, argv); +} + +int +sys_open(const char *path, uint32_t open_flags) { + return syscall(SYS_open, path, open_flags); +} + +int +sys_close(int fd) { + return syscall(SYS_close, fd); +} + +int +sys_read(int fd, void *base, size_t len) { + return syscall(SYS_read, fd, base, len); +} + +int +sys_write(int fd, void *base, size_t len) { + return syscall(SYS_write, fd, base, len); +} + +int +sys_seek(int fd, off_t pos, int whence) { + return syscall(SYS_seek, fd, pos, whence); +} + +int +sys_fstat(int fd, struct stat *stat) { + return syscall(SYS_fstat, fd, stat); +} + +int +sys_fsync(int fd) { + return syscall(SYS_fsync, fd); +} + +int +sys_getcwd(char *buffer, size_t len) { + return syscall(SYS_getcwd, buffer, len); +} + +int +sys_getdirentry(int fd, struct dirent *dirent) { + return syscall(SYS_getdirentry, fd, dirent); +} + +int +sys_dup(int fd1, int fd2) { + return syscall(SYS_dup, fd1, fd2); +} diff --git a/code/lab8/user/libs/syscall.h b/code/lab8/user/libs/syscall.h new file mode 100644 index 0000000..93f700a --- /dev/null +++ b/code/lab8/user/libs/syscall.h @@ -0,0 +1,33 @@ +#ifndef __USER_LIBS_SYSCALL_H__ +#define __USER_LIBS_SYSCALL_H__ + +int sys_exit(int error_code); +int sys_fork(void); +int sys_wait(int pid, int *store); +int sys_exec(const char *name, int argc, const char **argv); +int sys_yield(void); +int sys_kill(int pid); +int sys_getpid(void); +int sys_putc(int c); +int sys_pgdir(void); +int sys_sleep(unsigned int time); +size_t sys_gettime(void); + +struct stat; +struct dirent; + +int sys_open(const char *path, uint32_t open_flags); +int sys_close(int fd); +int sys_read(int fd, void *base, size_t len); +int sys_write(int fd, void *base, size_t len); +int sys_seek(int fd, off_t pos, int whence); +int sys_fstat(int fd, struct stat *stat); +int sys_fsync(int fd); +int sys_getcwd(char *buffer, size_t len); +int sys_getdirentry(int fd, struct dirent *dirent); +int sys_dup(int fd1, int fd2); +void sys_lab6_set_priority(uint32_t priority); //only for lab6 + + +#endif /* !__USER_LIBS_SYSCALL_H__ */ + diff --git a/code/lab8/user/libs/ulib.c b/code/lab8/user/libs/ulib.c new file mode 100644 index 0000000..e855ea0 --- /dev/null +++ b/code/lab8/user/libs/ulib.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include + +static lock_t fork_lock = INIT_LOCK; + +void +lock_fork(void) { + lock(&fork_lock); +} + +void +unlock_fork(void) { + unlock(&fork_lock); +} + +void +exit(int error_code) { + sys_exit(error_code); + cprintf("BUG: exit failed.\n"); + while (1); +} + +int +fork(void) { + return sys_fork(); +} + +int +wait(void) { + return sys_wait(0, NULL); +} + +int +waitpid(int pid, int *store) { + return sys_wait(pid, store); +} + +void +yield(void) { + sys_yield(); +} + +int +kill(int pid) { + return sys_kill(pid); +} + +int +getpid(void) { + return sys_getpid(); +} + +//print_pgdir - print the PDT&PT +void +print_pgdir(void) { + sys_pgdir(); +} + +void +lab6_set_priority(uint32_t priority) +{ + sys_lab6_set_priority(priority); +} + +int +sleep(unsigned int time) { + return sys_sleep(time); +} + +unsigned int +gettime_msec(void) { + return (unsigned int)sys_gettime(); +} + +int +__exec(const char *name, const char **argv) { + int argc = 0; + while (argv[argc] != NULL) { + argc ++; + } + return sys_exec(name, argc, argv); +} diff --git a/code/lab8/user/libs/ulib.h b/code/lab8/user/libs/ulib.h new file mode 100644 index 0000000..baca03b --- /dev/null +++ b/code/lab8/user/libs/ulib.h @@ -0,0 +1,49 @@ +#ifndef __USER_LIBS_ULIB_H__ +#define __USER_LIBS_ULIB_H__ + +#include + +void __warn(const char *file, int line, const char *fmt, ...); +void __noreturn __panic(const char *file, int line, const char *fmt, ...); + +#define warn(...) \ + __warn(__FILE__, __LINE__, __VA_ARGS__) + +#define panic(...) \ + __panic(__FILE__, __LINE__, __VA_ARGS__) + +#define assert(x) \ + do { \ + if (!(x)) { \ + panic("assertion failed: %s", #x); \ + } \ + } while (0) + +// static_assert(x) will generate a compile-time error if 'x' is false. +#define static_assert(x) \ + switch (x) { case 0: case (x): ; } + +int fprintf(int fd, const char *fmt, ...); + +void __noreturn exit(int error_code); +int fork(void); +int wait(void); +int waitpid(int pid, int *store); +void yield(void); +int kill(int pid); +int getpid(void); +void print_pgdir(void); +int sleep(unsigned int time); +unsigned int gettime_msec(void); +int __exec(const char *name, const char **argv); + +#define __exec0(name, path, ...) \ +({ const char *argv[] = {path, ##__VA_ARGS__, NULL}; __exec(name, argv); }) + +#define exec(path, ...) __exec0(NULL, path, ##__VA_ARGS__) +#define nexec(name, path, ...) __exec0(name, path, ##__VA_ARGS__) + +void lab6_set_priority(uint32_t priority); //only for lab6 + +#endif /* !__USER_LIBS_ULIB_H__ */ + diff --git a/code/lab8/user/libs/umain.c b/code/lab8/user/libs/umain.c new file mode 100644 index 0000000..9bee9ab --- /dev/null +++ b/code/lab8/user/libs/umain.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +int main(int argc, char *argv[]); + +static int +initfd(int fd2, const char *path, uint32_t open_flags) { + int fd1, ret; + if ((fd1 = open(path, open_flags)) < 0) { + return fd1; + } + if (fd1 != fd2) { + close(fd2); + ret = dup2(fd1, fd2); + close(fd1); + } + return ret; +} + +void +umain(int argc, char *argv[]) { + int fd; + if ((fd = initfd(0, "stdin:", O_RDONLY)) < 0) { + warn("open failed: %e.\n", fd); + } + if ((fd = initfd(1, "stdout:", O_WRONLY)) < 0) { + warn("open failed: %e.\n", fd); + } + int ret = main(argc, argv); + exit(ret); +} + diff --git a/code/lab8/user/ls.c b/code/lab8/user/ls.c new file mode 100644 index 0000000..93a0a8e --- /dev/null +++ b/code/lab8/user/ls.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define printf(...) fprintf(1, __VA_ARGS__) +#define BUFSIZE 4096 + +static char +getmode(uint32_t st_mode) { + char mode = '?'; + if (S_ISREG(st_mode)) mode = '-'; + if (S_ISDIR(st_mode)) mode = 'd'; + if (S_ISLNK(st_mode)) mode = 'l'; + if (S_ISCHR(st_mode)) mode = 'c'; + if (S_ISBLK(st_mode)) mode = 'b'; + return mode; +} + +static int +getstat(const char *name, struct stat *stat) { + int fd, ret; + if ((fd = open(name, O_RDONLY)) < 0) { + return fd; + } + ret = fstat(fd, stat); + close(fd); + return ret; +} + +void +lsstat(struct stat *stat, const char *filename) { + printf(" [%c]", getmode(stat->st_mode)); + printf(" %3d(h)", stat->st_nlinks); + printf(" %8d(b)", stat->st_blocks); + printf(" %8d(s)", stat->st_size); + printf(" %s\n", filename); +} + +int +lsdir(const char *path) { + struct stat __stat, *stat = &__stat; + int ret; + DIR *dirp = opendir("."); + + if (dirp == NULL) { + return -1; + } + struct dirent *direntp; + while ((direntp = readdir(dirp)) != NULL) { + if ((ret = getstat(direntp->name, stat)) != 0) { + goto failed; + } + lsstat(stat, direntp->name); + } + printf("lsdir: step 4\n"); + closedir(dirp); + return 0; +failed: + closedir(dirp); + return ret; +} + +int +ls(const char *path) { + struct stat __stat, *stat = &__stat; + int ret, type; + if ((ret = getstat(path, stat)) != 0) { + return ret; + } + + static const char *filetype[] = { + " [ file ]", + " [directory]", + " [ symlink ]", + " [character]", + " [ block ]", + " [ ????? ]", + }; + switch (getmode(stat->st_mode)) { + case '0': type = 0; break; + case 'd': type = 1; break; + case 'l': type = 2; break; + case 'c': type = 3; break; + case 'b': type = 4; break; + default: type = 5; break; + } + + printf(" @ is %s", filetype[type]); + printf(" %d(hlinks)", stat->st_nlinks); + printf(" %d(blocks)", stat->st_blocks); + printf(" %d(bytes) : @'%s'\n", stat->st_size, path); + if (S_ISDIR(stat->st_mode)) { + return lsdir(path); + } + return 0; +} + +int +main(int argc, char **argv) { + if (argc == 1) { + return ls("."); + } + else { + int i, ret; + for (i = 1; i < argc; i ++) { + if ((ret = ls(argv[i])) != 0) { + return ret; + } + } + } + return 0; +} + diff --git a/code/lab8/user/matrix.c b/code/lab8/user/matrix.c new file mode 100644 index 0000000..c5ec869 --- /dev/null +++ b/code/lab8/user/matrix.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include + +#define MATSIZE 10 + +static int mata[MATSIZE][MATSIZE]; +static int matb[MATSIZE][MATSIZE]; +static int matc[MATSIZE][MATSIZE]; + +void +work(unsigned int times) { + int i, j, k, size = MATSIZE; + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + mata[i][j] = matb[i][j] = 1; + } + } + + yield(); + + cprintf("pid %d is running (%d times)!.\n", getpid(), times); + + while (times -- > 0) { + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + matc[i][j] = 0; + for (k = 0; k < size; k ++) { + matc[i][j] += mata[i][k] * matb[k][j]; + } + } + } + for (i = 0; i < size; i ++) { + for (j = 0; j < size; j ++) { + mata[i][j] = matb[i][j] = matc[i][j]; + } + } + } + cprintf("pid %d done!.\n", getpid()); + exit(0); +} + +const int total = 21; + +int +main(void) { + int pids[total]; + memset(pids, 0, sizeof(pids)); + + int i; + for (i = 0; i < total; i ++) { + if ((pids[i] = fork()) == 0) { + srand(i * i); + int times = (((unsigned int)rand()) % total); + times = (times * times + 10) * 100; + work(times); + } + if (pids[i] < 0) { + goto failed; + } + } + + cprintf("fork ok.\n"); + + for (i = 0; i < total; i ++) { + if (wait() != 0) { + cprintf("wait failed.\n"); + goto failed; + } + } + + cprintf("matrix pass.\n"); + return 0; + +failed: + for (i = 0; i < total; i ++) { + if (pids[i] > 0) { + kill(pids[i]); + } + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/pgdir.c b/code/lab8/user/pgdir.c new file mode 100644 index 0000000..09fd7e3 --- /dev/null +++ b/code/lab8/user/pgdir.c @@ -0,0 +1,11 @@ +#include +#include + +int +main(void) { + cprintf("I am %d, print pgdir.\n", getpid()); + print_pgdir(); + cprintf("pgdir pass.\n"); + return 0; +} + diff --git a/code/lab8/user/priority.c b/code/lab8/user/priority.c new file mode 100644 index 0000000..d71147e --- /dev/null +++ b/code/lab8/user/priority.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +#define TOTAL 5 +/* to get enough accuracy, MAX_TIME (the running time of each process) should >1000 mseconds. */ +#define MAX_TIME 2000 +#define SLEEP_TIME 400 +unsigned int acc[TOTAL]; +int status[TOTAL]; +int pids[TOTAL]; + +static void +spin_delay(void) +{ + int i; + volatile int j; + for (i = 0; i != 200; ++ i) + { + j = !j; + } +} + +int +main(void) { + int i,time; + cprintf("priority process will sleep %d ticks\n",SLEEP_TIME); + sleep(SLEEP_TIME); + memset(pids, 0, sizeof(pids)); + lab6_set_priority(TOTAL + 1); + + for (i = 0; i < TOTAL; i ++) { + acc[i]=0; + if ((pids[i] = fork()) == 0) { + lab6_set_priority(i + 1); + acc[i] = 0; + while (1) { + spin_delay(); + ++ acc[i]; + if(acc[i]%4000==0) { + if((time=gettime_msec())>MAX_TIME) { + cprintf("child pid %d, acc %d, time %d\n",getpid(),acc[i],time); + exit(acc[i]); + } + } + } + + } + if (pids[i] < 0) { + goto failed; + } + } + + cprintf("main: fork ok,now need to wait pids.\n"); + + for (i = 0; i < TOTAL; i ++) { + status[i]=0; + waitpid(pids[i],&status[i]); + cprintf("main: pid %d, acc %d, time %d\n",pids[i],status[i],gettime_msec()); + } + cprintf("main: wait pids over\n"); + cprintf("stride sched correct result:"); + for (i = 0; i < TOTAL; i ++) + { + cprintf(" %d", (status[i] * 2 / status[0] + 1) / 2); + } + cprintf("\n"); + + return 0; + +failed: + for (i = 0; i < TOTAL; i ++) { + if (pids[i] > 0) { + kill(pids[i]); + } + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/sh.c b/code/lab8/user/sh.c new file mode 100644 index 0000000..9a652f1 --- /dev/null +++ b/code/lab8/user/sh.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include + +#define printf(...) fprintf(1, __VA_ARGS__) +#define putc(c) printf("%c", c) + +#define BUFSIZE 4096 +#define WHITESPACE " \t\r\n" +#define SYMBOLS "<|>&;" + +char shcwd[BUFSIZE]; + +int +gettoken(char **p1, char **p2) { + char *s; + if ((s = *p1) == NULL) { + return 0; + } + while (strchr(WHITESPACE, *s) != NULL) { + *s ++ = '\0'; + } + if (*s == '\0') { + return 0; + } + + *p2 = s; + int token = 'w'; + if (strchr(SYMBOLS, *s) != NULL) { + token = *s, *s ++ = '\0'; + } + else { + bool flag = 0; + while (*s != '\0' && (flag || strchr(WHITESPACE SYMBOLS, *s) == NULL)) { + if (*s == '"') { + *s = ' ', flag = !flag; + } + s ++; + } + } + *p1 = (*s != '\0' ? s : NULL); + return token; +} + +char * +readline(const char *prompt) { + static char buffer[BUFSIZE]; + if (prompt != NULL) { + printf("%s", prompt); + } + int ret, i = 0; + while (1) { + char c; + if ((ret = read(0, &c, sizeof(char))) < 0) { + return NULL; + } + else if (ret == 0) { + if (i > 0) { + buffer[i] = '\0'; + break; + } + return NULL; + } + + if (c == 3) { + return NULL; + } + else if (c >= ' ' && i < BUFSIZE - 1) { + putc(c); + buffer[i ++] = c; + } + else if (c == '\b' && i > 0) { + putc(c); + i --; + } + else if (c == '\n' || c == '\r') { + putc(c); + buffer[i] = '\0'; + break; + } + } + return buffer; +} + +void +usage(void) { + printf("usage: sh [command-file]\n"); +} + +int +reopen(int fd2, const char *filename, uint32_t open_flags) { + int ret, fd1; + close(fd2); + if ((ret = open(filename, open_flags)) >= 0 && ret != fd2) { + close(fd2); + fd1 = ret, ret = dup2(fd1, fd2); + close(fd1); + } + return ret < 0 ? ret : 0; +} + +int +testfile(const char *name) { + int ret; + if ((ret = open(name, O_RDONLY)) < 0) { + return ret; + } + close(ret); + return 0; +} + +int +runcmd(char *cmd) { + static char argv0[BUFSIZE]; + const char *argv[EXEC_MAX_ARG_NUM + 1]; + char *t; + int argc, token, ret, p[2]; +again: + argc = 0; + while (1) { + switch (token = gettoken(&cmd, &t)) { + case 'w': + if (argc == EXEC_MAX_ARG_NUM) { + printf("sh error: too many arguments\n"); + return -1; + } + argv[argc ++] = t; + break; + case '<': + if (gettoken(&cmd, &t) != 'w') { + printf("sh error: syntax error: < not followed by word\n"); + return -1; + } + if ((ret = reopen(0, t, O_RDONLY)) != 0) { + return ret; + } + break; + case '>': + if (gettoken(&cmd, &t) != 'w') { + printf("sh error: syntax error: > not followed by word\n"); + return -1; + } + if ((ret = reopen(1, t, O_RDWR | O_TRUNC | O_CREAT)) != 0) { + return ret; + } + break; + case '|': + // if ((ret = pipe(p)) != 0) { + // return ret; + // } + if ((ret = fork()) == 0) { + close(0); + if ((ret = dup2(p[0], 0)) < 0) { + return ret; + } + close(p[0]), close(p[1]); + goto again; + } + else { + if (ret < 0) { + return ret; + } + close(1); + if ((ret = dup2(p[1], 1)) < 0) { + return ret; + } + close(p[0]), close(p[1]); + goto runit; + } + break; + case 0: + goto runit; + case ';': + if ((ret = fork()) == 0) { + goto runit; + } + else { + if (ret < 0) { + return ret; + } + waitpid(ret, NULL); + goto again; + } + break; + default: + printf("sh error: bad return %d from gettoken\n", token); + return -1; + } + } + +runit: + if (argc == 0) { + return 0; + } + else if (strcmp(argv[0], "cd") == 0) { + if (argc != 2) { + return -1; + } + strcpy(shcwd, argv[1]); + return 0; + } + if ((ret = testfile(argv[0])) != 0) { + if (ret != -E_NOENT) { + return ret; + } + snprintf(argv0, sizeof(argv0), "/%s", argv[0]); + argv[0] = argv0; + } + argv[argc] = NULL; + return __exec(NULL, argv); +} + +int +main(int argc, char **argv) { + printf("user sh is running!!!"); + int ret, interactive = 1; + if (argc == 2) { + if ((ret = reopen(0, argv[1], O_RDONLY)) != 0) { + return ret; + } + interactive = 0; + } + else if (argc > 2) { + usage(); + return -1; + } + //shcwd = malloc(BUFSIZE); + assert(shcwd != NULL); + + char *buffer; + while ((buffer = readline((interactive) ? "$ " : NULL)) != NULL) { + shcwd[0] = '\0'; + int pid; + if ((pid = fork()) == 0) { + ret = runcmd(buffer); + exit(ret); + } + assert(pid >= 0); + if (waitpid(pid, &ret) == 0) { + if (ret == 0 && shcwd[0] != '\0') { + ret = 0; + } + if (ret != 0) { + printf("error: %d - %e\n", ret, ret); + } + } + } + return 0; +} + diff --git a/code/lab8/user/sleep.c b/code/lab8/user/sleep.c new file mode 100644 index 0000000..5bc56e0 --- /dev/null +++ b/code/lab8/user/sleep.c @@ -0,0 +1,28 @@ +#include +#include + +void +sleepy(int pid) { + int i, time = 100; + for (i = 0; i < 10; i ++) { + sleep(time); + cprintf("sleep %d x %d slices.\n", i + 1, time); + } + exit(0); +} + +int +main(void) { + unsigned int time = gettime_msec(); + int pid1, exit_code; + + if ((pid1 = fork()) == 0) { + sleepy(pid1); + } + + assert(waitpid(pid1, &exit_code) == 0 && exit_code == 0); + cprintf("use %04d msecs.\n", gettime_msec() - time); + cprintf("sleep pass.\n"); + return 0; +} + diff --git a/code/lab8/user/sleepkill.c b/code/lab8/user/sleepkill.c new file mode 100644 index 0000000..01268a2 --- /dev/null +++ b/code/lab8/user/sleepkill.c @@ -0,0 +1,18 @@ +#include +#include + +int +main(void) { + int pid; + if ((pid = fork()) == 0) { + sleep(~0); + exit(0xdead); + } + assert(pid > 0); + + sleep(100); + assert(kill(pid) == 0); + cprintf("sleepkill pass.\n"); + return 0; +} + diff --git a/code/lab8/user/softint.c b/code/lab8/user/softint.c new file mode 100644 index 0000000..2f14d15 --- /dev/null +++ b/code/lab8/user/softint.c @@ -0,0 +1,9 @@ +#include +#include + +int +main(void) { + asm volatile("int $14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/spin.c b/code/lab8/user/spin.c new file mode 100644 index 0000000..91581e5 --- /dev/null +++ b/code/lab8/user/spin.c @@ -0,0 +1,29 @@ +#include +#include + +int +main(void) { + int pid, ret; + cprintf("I am the parent. Forking the child...\n"); + if ((pid = fork()) == 0) { + cprintf("I am the child. spinning ...\n"); + while (1); + } + cprintf("I am the parent. Running the child...\n"); + + yield(); + yield(); + yield(); + + cprintf("I am the parent. Killing the child...\n"); + + assert((ret = kill(pid)) == 0); + cprintf("kill returns %d\n", ret); + + assert((ret = waitpid(pid, NULL)) == 0); + cprintf("wait returns %d\n", ret); + + cprintf("spin may pass.\n"); + return 0; +} + diff --git a/code/lab8/user/testbss.c b/code/lab8/user/testbss.c new file mode 100644 index 0000000..14dc6e1 --- /dev/null +++ b/code/lab8/user/testbss.c @@ -0,0 +1,33 @@ +#include +#include + +#define ARRAYSIZE (1024*1024) + +uint32_t bigarray[ARRAYSIZE]; + +int +main(void) { + cprintf("Making sure bss works right...\n"); + int i; + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != 0) { + panic("bigarray[%d] isn't cleared!\n", i); + } + } + for (i = 0; i < ARRAYSIZE; i ++) { + bigarray[i] = i; + } + for (i = 0; i < ARRAYSIZE; i ++) { + if (bigarray[i] != i) { + panic("bigarray[%d] didn't hold its value!\n", i); + } + } + + cprintf("Yes, good. Now doing a wild write off the end...\n"); + cprintf("testbss may pass.\n"); + + bigarray[ARRAYSIZE + 1024] = 0; + asm volatile ("int $0x14"); + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/waitkill.c b/code/lab8/user/waitkill.c new file mode 100644 index 0000000..9bb3f80 --- /dev/null +++ b/code/lab8/user/waitkill.c @@ -0,0 +1,59 @@ +#include +#include + +void +do_yield(void) { + yield(); + yield(); + yield(); + yield(); + yield(); + yield(); +} + +int parent, pid1, pid2; + +void +loop(void) { + cprintf("child 1.\n"); + while (1); +} + +void +work(void) { + cprintf("child 2.\n"); + do_yield(); + if (kill(parent) == 0) { + cprintf("kill parent ok.\n"); + do_yield(); + if (kill(pid1) == 0) { + cprintf("kill child1 ok.\n"); + exit(0); + } + } + exit(-1); +} + +int +main(void) { + parent = getpid(); + if ((pid1 = fork()) == 0) { + loop(); + } + + assert(pid1 > 0); + + if ((pid2 = fork()) == 0) { + work(); + } + if (pid2 > 0) { + cprintf("wait child 1.\n"); + waitpid(pid1, NULL); + panic("waitpid %d returns\n", pid1); + } + else { + kill(pid1); + } + panic("FAIL: T.T\n"); +} + diff --git a/code/lab8/user/yield.c b/code/lab8/user/yield.c new file mode 100644 index 0000000..a19890d --- /dev/null +++ b/code/lab8/user/yield.c @@ -0,0 +1,16 @@ +#include +#include + +int +main(void) { + int i; + cprintf("Hello, I am process %d.\n", getpid()); + for (i = 0; i < 5; i ++) { + yield(); + cprintf("Back in process %d, iteration %d.\n", getpid(), i); + } + cprintf("All done in process %d.\n", getpid()); + cprintf("yield pass.\n"); + return 0; +} + diff --git a/doc/lab0.pdf b/doc/lab0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..904a594a5b4a615b08d5c85f267131ec8bea726a GIT binary patch literal 953677 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 z z{B4sEb4fct2}b5jHzP0lrmtZ#rZrKn1dGyH0<&0e)-7NaR;Q%6jMUo=ASk;6?X+Aw5!~XE$Rc?=QxC45FLWvUVD_gyt@< z2JD^k7HhI3>^BStDMKkk4MOjwg8&X?cpqhwo%tgzcliqQyot<%J_6cF0&8+luYI!m z(cm5B4uwj$61D|@9zxn_OwpJVJM)4RESi$;n8`lZ!{(vIX2O+52&6_?owqiEs(P2} za-3)WMs(p?&W4*PJDUFkmvn5EsNpASQV~uGJMe?+SB;FLOOL+z`4vB>y#3UP}Of;+*C!3tLL8}5>zUg^S@Jc1GK)(2B z)%V{pt=#V>OH1tsJP!}gH`!4(4hQng%WQgKFFeZ-A2K0Q#`QWGMtpma-%Eh~{xEj? z^KNRE8)inDrHYws_LSMUk``T+C;8-jLZoR$WU=OMph@7nk`p0WegS%TbK-A;=P(L0 z*>tIb83od!!Z?E|c9*xeAU?vk9n6gI)D$@196}rF-t~8&F7b2OB*15+JjWI#?rj)*=pD?WTn_JQJ1W*SH0P_ub&4w*p__R zjNq)OZ6S_#kv55F4HAz&vuZF#)QLIlxAoF4XAiStaoQ1+7)dv?$$_&L{z5Z2CEe@G z!+HOkP;i+1v|W;sMv8d~;>c^O7eiJ&);Mm%fcqoxt@=S##8>Xm&aHJBZt(f+@GCPF zRb0iRRX*(63RvPqI+h;6X4?BKv~o=`q?Lx~#i&9MDXdXx;fI}|ook^*vv(31Y*~nq z+*DH;-8Ci}W0pbYTM+h3(Ddtv;q?UUJQ>$=L|J2z*zaZ9Gic1HD#wN_1FDMmQYZ<8 z9fkKT#6aiHs~B|5tgkSfCv?sC1(_{eP$`(OjRFv&v6DN?uxck_qCwQCA6zZfN)JdI zQ*W{gEqM1h+>FdC2r9+f!Y>b;R$`SZ?5gO81-Rh`EmL@cTTTT(jMwTdq)cXUwBf#8 zG?K3WPC;o;{r$P=_xrnW{1^FprtOa|Z9al4tagk5Xh22VW!LL`E$``eRYXWE-QrFY9V5D5qV^t154V0vsPt}P2KBm-nZesUJCChxuQJWKiIcenN!NQkIhA|E zMnkML)r%d_ZYac2C(?n3x7LPNIWjxYHTb}@)`Rji*@@D%SB_$iiJ&V~r-xra%uUL@ zD0>}Rks5G3vG!S~R%sFKbGBBI;h}I7oAE=gOj&QzigKws=EL{iJ^{s_)%M3K76~95#XPbe&Q%gI54X>l zX;cVKYvwR|-yP1|eKQbB@-`zU+*n2H${4Q5do+ekf9-4fjFH~Dbv8H_nQ$E>&<~Ft zZF4}sgYO`P?{`u&YH+mVp=84!`?!XM_r(+PX5QH8C-AEX>Lwo-rapfunn`ea*Jp?T zxBJN0JX0!R^0|h;O&kV}?{|OMop$Xq$d)ulk2H+7CTwVtPxHOJ;nC;52L+S? zzTL8~@w#`GK%|+COe5*xt$fHY%7ccbtctkDx9eciD>S(uV#suTngVu-J-1X^YDe2k zG3M*aWn~MPD=T8TQ~Q;l7Fc6cIJAT{S9?DDCYZGs>eLm*%GAg zw9rE=DDJHGL#)ig`a&eFX7GTW0};M?2osuT+)7W#yD4{{?+B=kWTm_*c;k?Q0+FZ8 zZpDp1f~_)zj5Vp^yJgHuBR_Rb;e(<>LXyDf=e&!vr|lj+Xj8^+d^e3#(hw7TRyx&u znYVO4_=M9MW+JHGXdn;Y2e_CH8;WytIvZp7_~Z6&>Xn1S!!nTMfV5bCl6|;BPe;pg z3wIiKfy1Jc%f55>s zQvbm(6cpJtGY*1JxsNm(T~c&>pw(qAjPFlK6U)iR@`MKaeVwmb{}S;7zI=`)1a-Qc z15{{>%44Z43%(x$hdaA0zJdcxNy27?pUN3K(CRWACng4l3_q)l?o4j)%{R$By41NS z%nB0%GaEgHvU6y-LzH#x-MKm!RKKFNo_|(2_#n7ifh#tZ_(A!i6lJ9n2mc8Uw+AY; zERAz*7jNZ>(ET}avLnK2?;R%TE|rjQYsQo0FH_S~W|fh8Gc$Vq)QBs!_i4IFF-Mg; z^D5m#dv5&ZaF)lBo)xIWTmyakiu|YNB-%Ge;bVeXKzH>=qW% z8Qpid(zFum{`F+3!l_*v@Uf?R8H97P81^dks`Kn0Z)N5)Pt-aY%gxgFk+FCLV6;g~7`SmK=xMfHBQkb%KmE`R~`k0j!*{ybRfT#hyYSA_8 zLXS+W8TS=;q`aCpAs$XkBiaDX<^mHadcj2F3QiNF|eBA1hp~D}o-* z9cOZ^ZEeY}0j?~OhpCXXxQZxyVvkp7b+Wu(lk1~n? zknhJsQp{hZ{Es>QdwJOr;(m)C5WBI^{}){dmkz*ZktwcuJN(V;WAB5O!YvS9zea!U zq5N;A0a8YOWsYuG$$$Fqh3;(#pQ^3EVoHP69ruR?{6jk+`w(=K*n~f){fpd1gG_%{ z&@WQCWK8*I?ZL0-UavR(w8C&f_MfJfhDrV}ypKf(jH5-|R)v`uPle}?3j~qx=pp|> z28nt$XFEpQt0c305%Gk6H!guvN-P1Xat}bh1HnT8`EIYRaj$B!2)x!k05FmPv9U$7 zE1r_;3n0QW&jP-dla*lpBynn~e{l%l$i8U;82v=iJ|N^L>I^mUBTISXwDjQfp0tEm588;1WE*Ie)*+xf|3w0a zm-m7gGXLa~{U-*p|Lx~e77RP$By)cZ_|qQ)#tU3xKR*}-Z`0zR6gB*$F4O#T3gUm8 zvSXo%Jt%=2^fPY2h=Q6~ikgu}PjGGND=L_(SnI~JEGr_&XZ*0@v%IFAzr&f~Ub{!o zP_lsPL|~tkLt}bPFxCUTmfR=idGWOV3)j^plzKk8hxuDcASp@YBFC1rwIn6wq_pj3 zu8_({-kjhpT=*#<2_pPx?y#`u@l+eHi5+Q!f2nhdVf*lud9(}xV}vxNmtBqXbG_wFIiO`R|$V|F8T*)=mJY90eE)KbY?*#HK$7S)-s)CG_ zLyh>XsozGX*H*Sp5gmQngv&eWQG@kV)DF{*Ec^cOKPqMwZn54H1gJojQAJTu2x;H- za&+Z?4NDnr7bR<`tEmcBubiqgRP_6yN7-ZaR4sXF)y&Ru^rSadlB8a zNbqjVpFcUGbMr&YQ^onCy`Z&sjK+Bz+8Yp$F6#OeWCnJtEJK%p-I)m?21+GaQ;eDy zn8*U0=I+Ix7kY%=I|ZA`Ej9km*kE^5L2F{jXJwDUu!z-IER9BW_m34}^d9ijU^!G~DE5w|2Z zRJ6Xe#>k$8oRzxvvIW4TS+qWQqc2FR(C0jn^tK`=+UT;gM5q)jld*gFB27!kJB^Xt zMW~E~JWlk(`;if0eNw-Zwy#6G#R&M=I>M3DI%)rly|<35W7*b4Cr)q(5C|>_?(S{@ zf&`b~?rwn@Ah<*D;O-FI-GXcI-~@-@HuIX~ti9IS=kD|FyZ7$<&pUtcqifFU>gulQ znq!Rm#`m#G7k$2?|HZhXM*oF0cphiBo1T)7fQv!=Qm%(^K}ly zNq@{Cd9#o^jW~E3>dlW7ELlLG*6n%vMp_Awq7F=SpLx(LDZNJAPv<^Xcb0xpeR33% zM_C_sXixV%LGiiu;f50v|K{67xz@%hQY$YuFEaz!0K0`fD<_8#9mtKK zx+yIev+8mJ3CjW$=s0KQkkUpTSK?d#!0B+_S1My!GEy@ELYSCtXu5PEOl!Cjg@@#K zuf(Tnd|cX&>BXyY$C9HOIu1#=YK`+_u=by(QOV=FopQ|VQ-}^+o^m7wat|-Q-o+0# zgfc`Hbo}Bpu%sy~m`ImIML&rfj_d(zWZBQ!MS+37Vms_((C4*@S_5^c6$E~P=|b1~ zOr#-1e)%ZQ%LYtON5-Dd!zB`}ZbKYLS>{*GSj)R`;rpNC90jH7D$>nalV#=== zF_u+$UuwG6EhwzE>~44Q4O@rSn~7Yz>Vj)|@XMA2RSFk5%hx-ITlPY8Ar+W_{lXu_dE~GS zji~>I0)G@KMwf&l&m+Iaj^TL6SEz=;=8a~$hV66B`&m(I**Uk1j81Bx)kfJ%M zbq#QJc3Z>L@kV_Tz<3{F+s#BG_#J;nWE5V3Qh9gfWzDo%By818PW&QXLYIUh7Y2SR zoa$?~SV>l><48;F9}qkpGR>`U9eS4ByUizfY1q7h;cdevCo1VR>F}Z49CyK48paU^ zL&@27v81Jx!q~?FtkRShLe8ghXqzhwVbfJyEXeiY!= z@u^zKrfSU$cNdOJlJG(^8=rRYOX5ix0AjI!X*UoR=G20md{_ZECJ`5;O9`yC_)m7q zpFjJCW`Ag1LO%v7{z%2OvII2adov(_$I;+ z&u=*K7>_n0^>-VvRV}1%Gw9L#>P_RkVvTjMq&J-foW?kEsfOBmUUPjd+UVPcd9D%i z#8?$+AY3%wW;H6662y`=-!Z1OpXDvO_}X8$HNmAm(tZqUj1QsC{>`wl2T61fGfXAr zbl4iQ$_!+j%Kt#mU3(*KespQ)LGPUp5nXP)&CzP?!nSz|O>}9p!x{YQ18>T1kIIKh zB}sMz?z+uhQvX0a3V*4JdXat21NVL{@<)ls% ze4DW>C#q>EqWfV9iB~eCx}vYYcHf{rlo1Pe@ab{imvEBcuDHu~Xc6@&X|@1t$UP%Y z^r2!;UYNib*6ZdPpAQ^<@|3S#I^L3Uv7>);gG`y{I6*;-6nG>uX8UPIL!BFZEzd7d zQvn1D6_jdEz^{}xbU~bJC8TzW6&YJ^!&?!wbd>ZPcVPOOK)N~V!mZ3Wr}srdYiuW% zE+tjC!k%o##NF>Y*Rf0XKxMRD1J4H*qRSU8!>xbuI4<5`lb`i_D-qy;Xc? zwf4h}vkadi3kbsoS)RXB=6WrZhKro;1ORa>g6aZzTZGXZ&g{XoLTg+n*MrX|(O9B2 zPH#L-Ywu;(x%pX4?T+OKYEi^vwHI$z^oEz}GgpBun?tc)`|H%T%WCiZL1zZkmf+7L zcALi=X$4J@$+w}PU6G~WIa1aj{0%>-T}gPBKAPjM9$ueqL4>>PRB1zfpQOix3^`~= zZ}YtD_i$exCZ+IJ*DK#CgD0$`^18v_ldW7U`86EO0RK~us*U9?*<93%4Y@PPn?L~z zy?Gn%9xvuLPFe3Eo9q2zA2NTEGd~>6)KvP#*e-AVJVR+2X8G$m-yXH2Ia5R7zNHI_ z?OiS9>qri!7c(`%M%*5R{1gK4jsPU;61SPyx}t1k===I}=XoT=XxNQgAj>;GXD!ls zAMmcn01(+7gG2k`xL(~@jdp-Y3<`>I$8KB=$!IeP(vc0>Hj7-C!1QEhfmPFFuC|2}xS5h)L*`*}Dc6m!aZMwlS(sbBJ(`dHF7+o$SJ5&4B74DJR9L7)7b(rh-@cU4{$(gWgE#oHE_~Ucl$YZ7~~p!Qr_4ipBqMC zHFk*DHs-i!=#;MTF5K9tksGFsR%D%s>CU7k#uarhBSoMrfRAgnj&sKX*r>6XN8A9f zv7Q|Ni$;%M&(PP3#~hU7nnZ-MD7y25^byyXnmBjkUlX zXH6X(*pRt7+ETJEN~2?r25@ii0IXr!`1@K{f(#Sv^QDI_sLHbh%?+{AQ(hRIP0ZYD zjs$U$Py!LS!j3>pCE48#;uiz{h7Q37cbTkxyG4=!!m~ox>Hz5d2}i`k$Vy_~+r^=h z#bX6>!|&M=tAV6GRQMa|pJuE`aPaS`jEa{Z5@xNEXHhFClCF~F+)?ZK54i^jqfv2% z^}$_ck1h7FeFLIP0LOer5X+h6_)bGN`mVMQ*{P95(@}EPym!*xv#eYaOABaNGz^sN z`eDt6!ylhw`brzlZ-Ujq92#!5{O5;bu5$~gyYL|JuE0dLz4vgSf_a|`AESftZ z*R>ZUz7}?3NxR#Dz&6kso#2qd@zd(rd8rlp^M&gik?Hc9WZRS#Ze6)Cn=Klff&km= z{eJQqc3|>!F=Tn|*4+Tp!UCm^ZApJ}{6Bo#f1jU9Z5hUOOIW!$$P>8ZRjTa<$1?CC zq6fxrBYc)?&KPTue9p2|Hw?ol2xj+gZ;!gbfpAE~(c`)9O^1L{Py+?T^KR-n@~kbp z*HP{&A_4$d&2K;SiJzW$y=rEzI07GGN7N&a(lPLslY%apDA1u_@}dkg{D(BrD3Gs; zjp8EEScD(7mWFS~PJ#~5!3|nnJ~=YEqmdp+9gdagK2jE@?y;A2^YqnCj?vouVmB^n zguHp*fEw#&R_^6PTpP`3E40&+e0)}aJ^7Ho0r46H$i308ry5F~p^uh8{MYTrRkD}O z;G5%T50`Z5ck=!BS8)&s$!X3Z7s=U*!re8vFRiOz?2jtcce?HOS7DGloH6e|o13^y zjk-Cedbk(bkodn+nWoGQ>P=X1_2QLZrQnnU5H<(0NZNz$&HucC>@_0-^;4XXrp8(? z)YtAE?k4jEGL*K+oWk7QD9^iQU97A!kl$J)@V+iiEknVOWS>{Xfa=)w)vaGB7us3> zb?4E`I&KYdM5h$s2)g%QcZo$12BZ9h=CBv=lNT9>N0zDvv!lEI0IYnzXcd%~B+$qS zZ;%Xg(@ZBl6F_06*VPx(QZRNJCfk z5xGajeuEPzfo&ga5Xl{a_mUksX1A9=)`N;_dP3sa5iZoU8nJ(N*+ts$`mHYS4^*|? z!*V5+w1@z^_)axg6f>f>+D7HDkdBUYVFZx|-_WP{lKWkXOG`@tSo;aX9IKes^CK2M z3hm*S5m&6SBkdlfCt@*?@I)?a?iKVGi)N=dK+=@8PRO#KSW+d9YsJu%v3bkpeOvC# zSPhWC)01%pEN&4b77f7e%Mb9kZV3Ak9o`iLYSINKCU<&1&nme<-8oc3j9HJx6|>eK z2Vr}V2mz;r(cg|Mgh1WfB$g|@Z?+0?LrRQzq;%ERAg;lwvtOU)C+M-oj1ZyYH6SHN za0VDlAuOPIiLUN4<$E3o+XJNUEi?WY%D>GE!)*7rLe+1P+p%<)m&S~()wUHV zWeBCpg%ycfm#|nJ!asF-t(I{q)k>GwhAan!&Lv7lxY71H;I6a0cW~U9qHSBR!Q?jS-8Lk$5Wx64cdzRbwCe^`iJ9gynrqR!WGrF^F^xUaO(xh6M~q<$r_)>Y5nU_NG&|R!@r;ZGi3DE zskW(gSt3ILS6L@3WU)(UjbutS7YnEtvd}A9Xo!6)yeY%8{4zE+g)Jl|)A=k_%apiS zvLwUsAbsYQhsN94pvU%pPw=Q(oqr51o$o+hRY7<5VE&x>ZCMNXvq);ADN4j-yaNFR zt#6Y??(aCVCCESXY6zE>GK;{#8ttn?{xMt-4ua+ZH@-vxK$L?T&o5L+s4rCb7h$1m zcH}hF$n_9}-}&3!6Nv{aJCR>P?HcL-$>C59N&7>m9ntSE4n=0TV;4)hI>yZ!yxnTP=L;>b#URE{SQ!IkeA=j7k=CbOUM0BXz0`#A0)<1JP|) zBeQEFdSnX;o;Du}nQ`$U^7l8}I*mICKUGqEyDU+SdTO%T8`XD5WmD{n#U?xfJ#o&cYq7p__}*6jFz2r zCP9eGC+&xgq9xyLjy$+Cr>wms=4L}QQd2boThhj3Wb4|3HTYH|{_8@tPD8LRVc1Nbiv6#`)BnDob)=k!DuXuhr`7DXM&8k{7BvVl zqjT-0%J@`Uz4_$Q_o%}^XF^l=3A$0zqUA*Nm@RS?PX@XC1?I7eIW)3JDL+5SLGZ>E?iab{I$R{nt$vqo8=0fSbk^03P+bLQH`S|7iR6aMp)HcSxs*fd5$^a@u>S? zYcYY1Or2Kbe6o@J*TY~agv9P6S)!o8Ol#O1$PACKn}c|S^Qef7AbX2g1ugZv89fp zWLDFz4Lv_(T6iHx^Ca$l6Qm+FHr*K_lP_kpRlM+xPtL?`h)vrY^;dtGQ25AcpCsSq zVgWev`N2X9m!xGqzr@Sak=PTHR$^ybb))9!WX zCh@nsTwYxGetBy>XjtS*aQIr^1;#;$wa^gSI6;U@Lg#{W_6nDGxZ6v%Kzf83OK>Mp z`6r0A+%pGy#Q#bGCa@!-!Z|R)h|AL$VzSz#0_@QPsGmdY^bDSZ&j0OjsWDg05X%wAJV=3-0bux0YeAE(bRwM$A0 zDcvF`5=00jzaV|KHozS_j{C9WL_BtQ>|DAvu4Z#1T){tn%Oo%4C^knrVY;zH?F z0`ovKlJGaTD{yD}Yk@QEe)URv^vn1vimW@)O&^|I$n;bcl~T=^qnX}mCeoFnIOIiT zgAKXNw)9JtX))Bg)eBL!K_7?Rl&Rv3g%r03SYVgw*G-cwG}!~m$^jVm|r5jn&`8>*Gs*2b6C98d|zmgnwDJF6;gB zh>yjNW2Pt6*nFMLmCDr69k9@7qhwE2+wy?SzdKQ!&`nct$(B`!}D;vYL3X}Qwi;5-o1&tefR$s~@ijWet ziKZ*hcTg|*vhzIsgiw@p&dse|+~JznxuPlZ&#HVkxGzVXiQ^XoduUH>XRE8mw4y#+ zyj@>S2V@1~_j%+D-Y6*r~sQ>WpL3(7{u=rVTCZ_2(vE+nuk*oIe&D(^~94vU)#{4hmr#Dxabr>HC1sv z0Tj$2qPQ7X4?xQHuNm{N&ehk>E(Tlf{yBmER)G4ahG&vCWejDET-=vz)!#&STp_f- z66XEmw=Hs7Y2iIj`+p(~q5cVs*Q(3NHTy zt#s}~D%$n>!tN-%2x+OzwP?hPzl7f0XiAeckOpx`e|It>S%@`SSYSENPy1kJaha$- z?(w;G8l|^EpN|l&NVaJAiqq3(WZ|vcqzLjrvFIT;{%-w3$i95n3ZO z^q8?_S@OXA6k2YO(83?npDi4n8TlgCM4Sjmd=ex?N+<7#MlMl~BT0+Ci3dW&E!sgn<}zi9P|PlN+FbMdXbcOgeXiuHzL=@nX5TJS1b`VLI1 z1!X#ZoVGU8wFG@{^Kvb)F~sef8n4_Z(6Ag%_ihd#I)?YpeY;PBUZZhf%aG~L@zlA` zJ-Hk4NVkJPLq8UQ|1@PTs!LsI-_NdI(s_DXPetQCXUF3p?^VL+a+1+K&+Z7zbN{Y3 z;{N;h&Q*t(KuEDG5rV0J4jxAU9Fq>9e4R3@6abwj(F5Fn7v0+NEoc{f{TNhv3M6>8 z5d)DFv_t^!0a(MQ990sae`_siz~?vs5B%A+`TY820WcCH0$jPs8W9w(jja6mA!*FK znrxUVsBU|M)Dx*|CjX=^Z*|%9Osr<&1#`#lWtgn$uw7(NWCKU-I)hi&75SO;bS)i~ z6guiK+P(Ki=Q#z>n4(9ylj`g{7MoS8Rsxx6`ULKS1v+<3fU&LQ>7am2F1wrEBqPgR z;ZG2H^c#y&_n;NLQy|zAY6Amoo=5+R<#T+-h>5lrCjGR7y$3~6K)kv-(Ljt2TP9HB z1R5<(X7sR`8`^1evmLSdu9a#rNl#haPowz7U1g96eJbv4kk=X$`nM4$2ZvlOj*&jO zxDRAQm=VhkG3VtBZ6y1$^zQ{qzYtgsUZw>~5JIaPEI2H_%mGv`+Q4>Ym#jr$J5qS-NX znW%j*{2IdjW6==F3lhLE@>vQ+b6VG~(m62(XD{niBaA6QcpnCKBGW8UW!KJmBif@~ zMnth);5SUNeQp)1Qw{G6zQ~D}KLfeIaM^g5jlw&5q$pRwJUI>)J<+Hv2G z)+#vqLZ0|79#3Cpcdnw0OZ6nSNsuISd2-D?K8AvnIX90=(v zJBMmt<nrjB36h9#lJ(1~z zy)cShHQ4*=13T3sp5jvBqaAH%tl~;Oe8y>d; zE|jbZf41?kB1BT4H8OP%XJABJhODJg znw_oH zO(TSqSz?@%UBDvxbLG+NP7%HK>9R6yX=t%1&uj89u0!nIv>EB5M%bo&K;wndrnC1i zj1#&TY3~a2%>Z(eD}vg;!{h<@JVF4UM{*MC?ge?zs|SI4#KO%cE*PE1K_0lSl`fMW z9^(k|5NxP~cT{WZ_RgqGBo{l6>z^v4ku))Pj*9ezD=qCRHA)AF7ir~A&SRJA;nwb? zlGtDEAgnAQL8g&-h~){yt$h;jssDNc zbWgi^+$7W8IY7<0KzSYpd0@I$QBlNw@n^A#+mdX-s@(uij-PYsXu?+12t`9><(UYE6>^?D=*b878Bb9J(6QcHW$ooi!5 z0qFlyn}+JOK!2n=!(es_q~u^nFoiHqv=qLmYm9xM`q+(M>3hVw=5|OXg6nRCT== zSpuJCG`Gv>cmJaPJEj_Et&b8#TGykKvLR)MM=xh2#^$ixsl4N;{Y0TbYsxQ3Sn$5Y znvbgqAh<8rJ0A6kZkmz2(n%S1H?qT8Tp}V`Q8M1+5bm}%@{!{n_2A!Gso90|e;J-a zok@?+Zag6Yr9YVSGL#w_ULK!aNmzr`-plm8^gSHw*re-Jz;z&a~Qko$#F=R-*-&AMo?%aiXYH=-E$ zijaB64&zAG4|kZ=Dhs}-Cun!WRay1*dPX(wBrhtji?x}sy^NE@k;E+IUEn8a??Ua1 z19u#Jcu<<(C}foSMHHf=^^txUff8z8Ay(Hv*^RSsGaA8DSd7|)N0}9kH!|lukMI*% zZW>#)eY(`S0n1zT2eL+5U2dE-rp5*(v}ZKDWiEtjoWamf^P-iP`snF!z{;A+tHwS+ zLR93wWcZyzu_%PBy+Ic8((%$U`9;{lO+6C&2sz#lH94XJ9sw^Gt;;-bp&)DW&$CR2 zp}XpnIz!gF_*;G}f_P0w6dpz~;X6Ddd{C2`*BQdojA{!*I#cCl)-i-!NW;STlxQs= zg|*kMr?^4+OWx^sT2o~O9d?XXAJsa)+sivVSM4jTtLiM;Q?Hw~a%0SRQMT1IWJgG$ zzy=z>vD8ByU14Qrk{ zKZBBd!83M*a-8oD9k zZRnXErq)QgLa8p;%u?y~t!hk5z@T%)yQ?#!=uhS1;@q@cjLRMV&8|V+R(NYO99D_- zKPX%SK<2pQn*kQwKPD;>V){5EHp1gxb+|nY42-fxLF%>oQ+v}1>(ZqeIUZPPvK?b3 zucYzu2r)$XF+4*DPF~JNsas8=wb*Ux3d{6-$!d4RBMzxQ+St0c91E`CAn2{B(=e-_ zN>r(-Pg<(r(o9pg%m+_z#P%QCXF@fDMkr4i{a>W6ysRy#Ew!!U?^<)Et9+sI>4IA* zo)sO(-E(`L&YfeKQ#0Qsl6rDWmX8<&(>R8AMO+)QK2#=hPGHg+vT(U@K`28oP-HLB za!?+i!Mo`j4~yZ*Dgs|+z1}8m`iyx`nEqBS_$o$6ob@WK!P+DD&DGQNR&$sMlh#`Q zF*coUym~}a`c=ek@5z<-mls7S6J_0tHNn-{PoBTDW>%J7lFC;Jzf+Gj2RwAEUJDe? zXU|w<^2OYWw8V<>zbPgT2eWwg<2?>Wvf-@~M0Y~7ec6x?t{`>4| z_XL&?Cjng`N9b?b?*)_WW>Y9niGd)-NzkY^-jZF}V1K@wS>yeFdoNyrp{@N(JFFdu z1VPk2T0MZEFM4ddl^H#6@yVVh5;RsC$L7Ncio*?YO5x z0)R`QKVGWvtOdaPibnK95CB5iQ`MFAZ~%nS>9O+iCn!sD0}#151RSAa{{LZz1l>Y_ zK9a}xPaXd1rv>?NrRY~oL{n#!Q~2e|&2Y2!S9^6oRR+Tm5MAAhTdUhm}etvS2MvAQj)8Ys;yR!L4iwb3>UmKPem3~RKCP-Mr#+n zUA&&(SVw)`-L0neVk^D{6(%uYIW3l&?Q9hUj{tL8Zn{16rLJ*UThs8JUju1Cg`+P6 z+)~_%?!t1T64od3;&P`chl~BUniZre-j*hvD1c%4=>ifzBU=4YM{`6iB|J{5aKMQ! z<1&Kzd?%l$U;q?z`mhago|s0P-+e3cJv*Q+AYWU&s=GiqwEQT?!WvgOVvwpBOE54C z(BUJzj9!q>S(LRyX6q;PO(L^v^?U6BGMlZcVc_aI&bFwwQma>RdH2=&CDv=j8wCoC zS9LRd*wK84++{Y%A}0#LhRFvti!(o7*=h$h2Cq%CM&sYWVpyg?T?#%MKObs1I$-y* zvPv4Hqc<00lte>wAE5N6lhigZFi+7?9ag5FNYHEAkH+(QuK)z=uGQP{`tSWsgf1_H z067;$nbizLF$cJox&W$Fw}imUcWo01{bO^=m4R<}IeFt2IUYW$OU{IKZx`|5ZBU}6 z1#O9INql&o)>nWUE(3OKHfuya8{l5exJH7AmZ1aw=dyqPfY9L)7{Zt?NaU^u19>qC zUIjMg?{#sZ(iK;9Bw=D|Dixo=WR9}QCoJ&H;F_xm$I=hr`|Ow4s@?hzaB$tzRUiX& z@PZ2WC>emrOb!I+YXBhZK|%$A2Otc+6Z)hHvo!Y=oEY2quoJlWKbDN)iC3SFH5S^Q zrxM(h<+pr}^kC2%_WeY%%tgX_*7>DkOC7wX>#hd@HdB9hh2*5Xg$dw0zY#iN1bP|< zmGn+17f()1rdq|y!Mc+SxrtO{&LKR!P$&R(?U3xyXLv!i%6~}X67PDhR9C73Mb@lb z=Aw-0TK(-aAoTDqUe1~+Z%@x8mwrJ&|u z3C`Oq2tT9He%u>vf8Hk)Z|D*TZF#s4Q@+Ti?j9~|Bg;CV533T5Uc&75T3D8kTeyAh zb8X7O06*WLn66k279$!e)z_y!zb`IFFftuy0B3JUhS&Rw^$D7RdHnii!-dd(%*>^5h-PLF#o!j9r!D5=4bCsi%qe?z`=mSYfxbEB=r?AHOPo zV0?aQ17!b08=&MV@8(^0=+mQ6YD_pl%r#8tz6uaM5Ah~pVF52xz|Lp6K>Sn1bo_*lwU04bdx~n@vjE}m8<$<~<`r*Mbayvh^e9E)}Cv;`PrhQrmAt(PViYqDUPH5MZfn?b$pz@jcrX^4px zQEMp;%9FIJ5^fmExcj%c)8A~PZ^7RNg|3SYxkLEz*KtM6I09;Cn#wZ>UN4kneOzRt z8&J0+wwA9ff6X#Tl`VV-S)L)|H36x>-Xy&fBZQbT`I7YxH+`q|)+tK#R$I|NYbY6B zX*a75x^uPww#PaB+tm*R2yNowkGtR9V?+>XUWr(_7<*{bJEP11)W_@+?+2-X7fPbz zJD1l)^P}QV{Sd@B?r{24WTKg$+dT-n;mfxfhqE=>May@gVe5HQqjx-b|{T zKg9Qz_(O@QmgY<-?@QTqW>(^IuHDvFvhpnCp~)9GrCnI%U-Foe{!sYNOqZ7`HeOB@ z>rsQssF`OtdTiI2l%5-F(S8zn?Us469k-kDIqEc*P3Gpu*aJ(r83aUAQ@%LIIjx9I zt@0hTs;mekG%5emlrCQI4?bTkunxeelZM@ZpfuhLDhfmq_ls~LHuG1WZ9cqR* zx$)gex5TttD+LepK_C^^GlVGmv>UsU2v_Pq3Wn*P$E=voVz7iB^&w)`S3RXxiY%z8 zHLy6#8~MQ2BNM4R`k@H6$WWy7@`0y>K%Hly@m3k47eHE>Q+PY=18)-adV07NL$Jtx zsC{dIQa}8%h*rdb8}wuo1gBX$Q`>5y)%iWKg0_N zGKrTh_7+|FxS3x+&enG&7fX`EYqo%fiMn;|?6Co@BEO_asz(tJ!4AuWjZvcpw z$Ymh_NdaHY{le@Z++RgO?npKOTi_Q|rTuFDH+)R*?f^i<{ObDSeg4mM-7fX9WwE{W zDkePsFOo>BpZve>SVsK^8twZO(Te2KfyQms$jc5p1-(81Jiz=21<9$!cFjby`_G#| zJ^yJPE0x=i1J)~rChE-pPpNjW4nVl$G4Anee*N?R%E6G;1qCyee<%eZr#ijwr1O(#~k000g4zQ|yc0RlRzbj?LsO_A)Fl`+V)9=5|HjVdoix^~=PPv4BW*uls>S4d!3 zOrm4P!{;V-bQpxGB@Zbq+(L=JO9v$YDC8rQZzL5kW#b_{m0 z(6}#`{aO3F&0xxG_jvQmj0}86F3CL1Y^PY9$p}E7R?~TnQpy)>BYYT3kDVVTzO4?I zAJzuj%SoW*L>@wN?9pe*BHWPL(#?XrR8K1O>aLh9`lkq5#$lCL~%pg?Ok(R5HV%;QgUS&cEf3KBQ zb^5ZvXVIpjW`03|x_fJp5GNWY?#!Wv*veMs`RFRSsMZn)2LaBZUm;KBbLHpB4YkUt zoDY%&mgtvlR~(j%T}^Ccr>410Ve@Zg3;OA~Y^?0Vo_sOjD)DWOH~3a-KD17k`lM{? z%^kgr6Y>zU4aE!iSH$ktjx>~0?ECxkcE(e04wZW<2kD44u{V(tP7S8xjan-;F&!eyXfg|?T1DR=bTZhDgao+{QRNmzTBoLuo})G0=la?)8lEHyQ8ips)Pnyr3; zWT*c%TjX@Cx$V~pbKT}yrYy*03VWTb)zh=wVmB^)Ax--c==Ev^5}*NPW2 zX=mE0Pf09b8R)=O)*Wi-WaHM&rw@~k?u#`*E*AFlbfwMp8ir+kwrvQ zm|e&EKM6hfQ@-#w^kC{&2KQzwo#|g-jYaxzQbzgwN#nPn*34b|_MRcc&3JA}`Al!t z*2^a6|CmWPA-keCb6tfoK0x)4>_Gw&%fA57M%X7v9;qs^y>qd{uVtSuWo(5?1gLGw z9n!%WkoUK5u5b?yEMJ>p-_k%KEfj0t7dRjyXbVu4>qf z1%MVCv9iv+4A@YZwe^&71e>=y!$QL68ODIAnGX2mzHb0;$AtqM3VogdAX@?Kz*WGN z0sO1~uJFI{9NKq2WnAO@^8S83);~(h>B)WGmTrNRF*D_8TYze8_1Mnq10=9<9WWYp zz-R#G;i>|XY9Rm00cg|EWqTgH#UF`sn91&>*ZD4?Wz_;hRzUl`F-_8I(`d1Vr z>NB8z#RY^e0CWDjsrEA(cv7ML2&i{zoog&ZgOG&_fU~q1J8~{%^=`I-I=}h`=s)S* z>`+1AfO*h6(+#U1ZE7#2sL$_H^NkS&=F;DP{6+u_oRuY*YR0t^a{UujklcRV6icA( zo@gEp%oMqA_qsYiRsndo|F3Rc$i8W*pR(@lW*fYlTUSx(q(8!TRCw+5s(ahDwjY^J z(P?uKz$@)O&N*klFVlp<8A2Dof?4WXcxsZ8jhifZn8Z0zT6TXbndXyM%mXT4z!Urx zr;+(vpIZ~I#}-}-ysLn;GQ|Uewp_ntO($I8gg!qqlkf*f;KnuyefZc zyaFx54h^lJ->o4Ph?o_mY8wqk0HgZY@3JIA;pT#F925skKo=AI3RW=|)WAap@&E*3 z>Cx1u^KBTAq78k(yL&+8>>&A?kSj`K@L~_(7nqe%z#6#W8zVn#K=tp)Y>D~#j4!!7 zJoY+H9nG|kPN25=(`TVI7w(^+`Q3J7fTtzh5&Zkr*JkPBUBZlsNdo3ix()*5t-`OT zI@J)G@wa+RkK+P#`gdGT>(@N^kLmdrU0I@zl1o|euSxf70F{fgdy1ahs0<7`_EYY} z_GHW^7wRlpEO?I`#12d>iciLC+^zv^LC80i64bhFhIEHi+ye-Ox0BOoUjL`8|C%I# zr2^;0zf73@yN&oaf(>Zmc#g04-oT5eOK_7C_FGT(JDrz(S(yer7Bj`zTQqYRu{Jt{ zzNg4~==hM?Szp_->9NZYLyhh)6sAVl!98%M+)D^J?CDhFDK9bi7d&~?7W!1Jv6#`= zMg`~u$Fx63DlSBYotIp{N2z*9pmrc7i@!p!6;Yb=@k=6v#D)}Grg(Bzxp>2B#ZOyt z)a5xFq<*Q>RO+zEfMvt%6c9ohnxQWBAdN=Ks#k{_nzv3ff5=712GzOPxMTFMfLj+>_ zEHAW)>@E90RB)J{3iNQfU9sNRO)SArFe=K4gP#z?Zbwys~IaoCnr816W&~Vb}Pl+yfblP{~XUoe|5?#U;C=io`MkM zg(*zrVg!guieLiS$rlBXNG5AjhjKxKLuCuUZOk=qHmXHj7Wad;L|^c#2`m#=54>utz84 zeUnZ^=bQ~~t}|(huIHKRq^Cvr$y&C@Y7=au}DRpJ_~`RZ+s zPQs*Z%}zlLZM2wndDB8Zp-*Uw)sc#yj4n58U;7)Tx{>%Zm6J9!&Soa;y@V^5zdC+F ztQip>C0t(W2K%JB!ZPd0$%=F3IkepptDi&TO|M)aK-9cCQ@I3R0*&P7&(V6-Q62W<9i0}qDs8F$M5zAiG^?15B ztMnx}^!rq~$|2$N3Luk8D(Dt$v-{vRj7AMJw7^H8hdW$ccPV0bjPhmyC(r?FrQ?Mi z3ne3t!7~`XHCh6 zrN!JKJ_%QwgWJRc*}(COeGZHTy@uaix7uxd+V@l10qaAb zK|AckVxthG;#hd7B~ixL-tNUs!)=5!c!LPR2lQKp`D!SH((0s~Fj?v_TPvkcbczNj zEb)tYTBr2H&Rh^$Q>=Qc8eCOXl63|;>zKY!>V|TmwQPbBmk(36*!RcK=6g-+iAkqy z<&(gqFXs%yt47!GObKr9dv;>oDdWn3hc4+03{DC99fTU{ojkA=S`LiFTSxGB4xcdy zKLdC_T<;cC?$$$0KEcS(RV;>9VMJMBL7q*&LGOBwGd}+)gImyoY4mLq8!F&mB2^H3 z|B;)d@)x8^>kR>_k^F!^$=RU8v38?Q63Cx1@Ogt=GX=41_cAmF5|U5Fv@A zS$)rk_Q~w#E6soL;Nom$7~mQyH!~PsgJ1o7)o1Jf)Ghw^Ug*DNZULFy|CZGSWP|@# zWo}vjS>{&Jz|4e6(ZJEf)|vG2OJycy6DKG(Hz_O6?;{RU zR_5PFEX<@Fzghv>Rdlp7dSl|O&7>$U$)sZ9?)>}0qQC!%{r)GTOA1sKv9+~xcG4#0 zV1I0el;hX6bV-@yO^huJMD5(QfscWfc{mt>zOl0~as%Iq0o?}f)QObicORu3?Og1E z`%v+)H(~l!V0y!(;%H#&WdHa`j69gc-Y|)qxLO#QC`*X~7gaGZbONfpadvbua{m3; zoJiRP1%HVO|I!cs)$$)o!jcx&&L)mblGX;!CgLVWcE%=5awfKB&gP^X%skwWr~RSu z>;7qm-^z|q%JCcAcOiN|?l}SdAl-)}n5e-~BXqaQwCX5H!2w7|oP`Xc#2zJW-PxLt zSMeyy+Ni zIp>$6SH%(WEryn7rU`dMpfOqsAp6iv9hS+3xpmn~I&+ zs*(K0J#roBEz<3=iZVR;?Mg_=)L)!w)k91IvjWM*@H1_50$~uyT4OiV=?z_$=x)IG zwoW9f(xh}2Y<_Q*bkyH<({_7lD^70QuAQE)dB2VCy(j)qmh@@9JNahqncm7$`@FT? z@zu8n7cMB&cjScxM!p?FP?Z|cOK&U{dAIP^ue5I-7!gz>sXx9!LhUl7NE&dUyu0Pt z`*;*NjMVVuz539S{Tm(X7f)|bS;!1a6L=_j+Y$5?Ed<&TRCv(T_6Xgp4bk6=(j3^$ z`7<1d*xPx+n|XyicW+s6a-%^{J0}?roGTGneM*$rA@Z)D6?28h94B4a>K>uSx9lgb zp#B;WV^b$z^vasQ+bGm$|4-4KS~K!1RW@AtimpK?zH2!r#^Ub+&1?gTWlZS!l=i7evs zesAmn>=$Y1{Q{KOs;yaZ2FlqRF9v&@c!K+?kuzjRBG1tlVEnNiZMB?hE@HSY$YjHG z$btrPLPIbOM9_rHB7_R>ec&<^9XB4<9oQzh%+tt3DDf#HU~O@vwJAvOEO-&zq+6h@ z;2;OWGH|cVKY&0NO3wYfLoltAhcqa=Uk|_&h*#-I{?N*yWcnS2ekPp>!;BP#6q$)# zj{VIrI}H2LWf6&Xo6OE~Y&7h4jj&tu$USfE%YKIUD^g7u{s=xc zu!O|WK`1g)8NUdt^wvh)rdu=INL{6ux+;$$zu6DY2n}bIp?Nq!t^liG|;$Z ztVPrj>AJeZyn`w8NG$FVX+}&;C9^BpC-JRg>?(Xgu9D=)haCd?PcW9FDeD|Jn zd+zO?>7MENgAKc?)?V+Ds{P}6-dgpB{0I)nX#WEAmMRDi1}5D7BNB509&XBp09i7d z1B}ple+?A4Lf8%(!U+=YE#ir$;I+sU+RZki4hn#w83v4&11S$lc+{=NxI+P0B9ieXO2EgKI+xKrMr~_RVmAAX;KL9fBU1AcSr3tJ9 zAqk2kG~_yqJe&U^ABkDt8m^kZqyBK9^f`^I2YNvTaeES(2Pg*MRp>l2lk#@DYw|=C zR9~*L5t9AOn<-=^d7KseRcq`ryVJATarJp!N6#qO=QVzP%>a=dc0}KwFvkp)h==o& z6tr}Fu3np*PTsA5B}RE;imLO?!DpsYJ+y#htW@DE2>v^_kG0`;$Qg5oIBAC4E@5D$ zn;~YP(9p0j&x~pFjo~a38bBtRn^X3v{mX8{g$LEaQ@L04P~KU&5rLtqK_{+ZbgonJ zEb!)~gsi{57(FIz;}gJ0`Sm9Wp>+_tlk_NvIS@7YqHXsV4JWR-^?Xmaq?EOgfh9FT zA`T0gw$On-?N8Uj>mvb}fE0c_3JT)L4|UE@CKj*;ZNy+=TCC|p8DV5T2bMe1-PU?n+$4H7 zY#*0eM;`$>Say5K(ehD#n#*`n< z3w@$UiELbYt0%WIHWz@GtJj$3hdw}i$EphchIj71&-&TI-AoP7#?0o;ApWl@bgsIy zdGv=7V6SQylhyM(GMZt)k;hHt##yV+*IZMFRrRMy;I4wECh>@@;*#UTjtz|jM3klE zPbUn#+%=#BcN|LKmjaCS0s{c21$)ghO#3 zc$>+IL`t58$yJV4w4p?ZTG8`#ccTpJ(PsVvn#||N!UJIOmdGgC2oWH@kIkyli!z8{ z^T`@>LMuA@-fEqUluYY9(yKaT-vSsF)w$UWB4^6r?5w(ATE*c7DZ7&R$n1qUm^I`( zM=DV(QB%kA)2eC79iu2y?x6bpfVuLKdtt$`jw$eR<#PAr_tWFE5IduoR<5+^BXdU3 zOghS`!0n3_=sB32gn4>R2i+heruC;msk(nGUh!}L~XRg@CmwMV*HDPrj zl~6>jWrbXGZ1@!yMEI)G>RIYgGtEX04cRS&mz^ZugP4Qy3yWQn6tFmF?oi#s+bd^| zwGjY#>PsZ3bo4LvggZ#ztnCu2`cdEMY*biZ_=ad7d9e?`E<<^vlXWPxHmtWn>EPj5B>35BzLUO!BGZB@Ynx5Qw3m zVkZGDgUOg}UfR`Wscf$v@IA+N-y&K1O^~5HwZFG4}TCDC6GKi z<_<`!4PmT&FSqFnNL?wYJ5C5#(=U7Mi%(QdH=1$6rm`Nk?`bhPn0BQm8%Z}$7S0_S zxh^DR>i)=S{%or~bd`GG_egk2q%3m^wA}BIVzn%f^OWzj+lC1~PP#lg{>#@=nE~S( zIDu)qBB^v`BX|KqS{RM0_QAqfya>JiahV8Eu;ePn$5Sm=ZB%!QlADr4!l_Vz+83!= zKFF|Z!KrO4W%w(GS*?cYHSb!^PeXy8h;GUqWy&*k>l7uo`Ae!4TUoj!)tL+3?&LIQtnrZ` z6p9U!RabDY^hLqt=f00e&Xn_HVqnOBkW;D8gY1hjsgtm?mW?jg(dOqXGqhK(b{}@&BpD1(!hDJn0V ztmWsFBylqx=QodwOV|oJ2cQLvbdv}{%C*Lw?VF~qrY58H=F4>Aeb@wSG+CH%}gcwS9@Ik6zvTY2z)Xj7r zju!1S*jgXd(K*KR?97?>;|#(FT5jfh z35Vnabdhl?YN@zKT6rWDbs}cvfhsF%$47Cd;&ijf|Es z<*ykbCM)g|;pK4~gob|Rfotmj78Y}dfB_~q?G>AE#C=*W5|EE}5*jME(B=v4%E7f8X9B zKD>yb6CaP>1l&ti+7TiKL)yq)wgHWnd3>MUsJ}=?ymWyA;!idV(y3=hh;XEA19l^rl2L{Ez0T3U}sm zrx0_feswy^@0P)q%3KSkmLYXEky_sQ?XxF=WkN0GutsgvDZcwwA8T;8hKltWc()=D=eSCfZa$A6pU+VVi z*S6C24guD%>O&XLZ3&zs%_z2OIE3`vnKr6C8r?gArb)?s*bNz)^KECIOmE7s3O=fGJ@}KgQR8kT4U(!R;@6v~B zhi26&9%hW}%qnnpap+3PP(NFz_Cljd&a~Rxm{9a(tpn#xM-Ca3*gj7ip_>w?Cgyv5 zo86;>qZt~t!o9O0Qnz2?Q998tmf@_r4YD|@rC*Lo5b%c2nqWCPE^X*4j?*fFRn_e^ zpH@b;=>tQ>ii{D$hFp~Qd_4T(Pyc{O z462F6<}Nw&R|F|hSo5yNaHJKC?0Yj=%QP~T9H(t+M zV3FW!+R%MZuuF$blonsY=p38&L{}X5uLpW%aIf|Fa6m-}1nRA#zP+2A{N)9C9*=~NFD1oo_Y&30h9i(8# zu^0#B5xdgGy|vKS7d|3WlIS zpC#~P6mPWQPZ^}x`qlyrJ%bic(3$FvSO_#s2c!|1`HHT*pPlYFt{dV;_W5%5hEo8I zDa;fRDS}u~;G8GV@or>-`f3{2>*>k883L8ZCt{2jB`m8ke2OIbTuH7}VQYo?aN8pB z8WMlVG0g8pYZ5s{y2!*4T>f)=@0Hc0Z@-^T@b9UbTa(MbXb(o>we6{G+|^NgGL^34 z*Z?5H|4t9lRm)9G+NNeMsGrAy$f9W!ag4B#;Q1oMQdDQ2fj)WFD5H~Cgrf(aDn@+M z^z2UFw7A~PYb4DJfa@`^ItZrNfu-RT$|;pYhQ!5!lRc2WO;y*{q%B1N4RH<-ashlP}uGg)<19unbn;qTGXXHf(r0Iy%Oh;Q7&3ten6^ zWbXvL1Wz!Zdcu|(#F2@dYGNk~FUZl{!)iU{P3{(1^-zF!JG|+1x`5PEWzlg;Z5cKS z86%tZOXF~j`=Zy1qR^wduZq$+>S2o0PbmQ&j7;JISU;&N71fQxk#_24&w-kVc%8r; z=!iJ__>jvq;Oj`vjC~JL2WyMYzX$fiHWxOIEjq|(j8PWjoKE*Vu1Y7*!=j1G+t4b~Bad(*5qL z!Bc^z*pJFg(}248nbA@=TU_jv5+3R??kPCOh0`;)X{Tg;!)9|QAp^f`^}$!XY5DE@ zF3}Cfh0QPEd+FiM_0X6x>~uK$>{dd0e{dsN>?_4q48xUcU$^XXb&z|l@@W*u=Bw|f zRXa>jmsd6PuC9!z^=_;0(GN9wf`{aA*`%)^oZc(TXYK8p1i;l|iTFNwI!}gi-(Mbj zY!k6!6CY8Rn5&3y@0Gzjt{<`Dbp=M4-RsV9RG736A5Y}f#(Y(7sHx1m89ddM-6hcJ zuIF4Z=PZAO?Kkiccs_^}+Q#|MJ$(w_fq64}gNV6eTKR(t|HgjCHb#F@!`~3|Z)&LM zY~b`8NJ^R8SbpGGLnloFCMG(z5Bkc^NXNnWK}#9vKHhM!(y=jqKurO~4}z&mK>r)d z5-bR6veXxaZi8pHT6jrmQc|8C45O#B;t1O6VO-&^Pb0{V`| ze>N+bTN^tPGt$c#yL`MV*jnq`&{C0k`1^WUhLfQ^muA6)#O@bd42{>1Y^zW*S1ZGwN=`+LjZ(f*I4 z`nwf^;zGYe_}v)BzgYO6)_iPMvK18aHYT#SldS^*>tBRl^><7D#QHBi`@6{BvC29a89RLR55?d8LrL&4^G(e^ z&J#BZ{*QCS_+x{jvz?ulvGs3N@iE!|z14rJ`TLXpZH)dsjz6|SM&H^P@Sl#)2cb80 zBw%6$h&$<9nH%!km|7VV&;$5?kB}1qI|su*M$3tSmXU!KAgFIAW^8V1=0w2C#s=X3 zYN~2(^a1sm8QB2R`mTSyVPs)p|5LN-Uq`Zibeh^fN-?s~e~1A6-!tN)(8tMSY_0P9 zkKg?#@W)-yew6-G*M}ydr~h5e$I0<~_lHt2X8hfue`d?Sbi;4Q@-N-+|B2jT_;5h~ zFg|Pqe?PFi- z2B+2jjgIeK-vKffvu|~5hbtf`*3Fsj60=h%8utj+u)duWO3mEZ{NhaV!`v*bGo5CW zEoY`ReTH1ZrOg;9SyCP-CyHQde|tSWIC#pqUChArxV30nsWhd3e%p_rW^Fe>Rb|>& zB3&4l&|zSCe))AZu&UeP$*}5OpFQC}IqmfJa}1q;%4Q&Pn-`I3n<^D^h?=YY`QxzL zMSPnsAG!L8xzp9)@!PBG>*wZXmnk8u#}`kJ`}1w@yMw3Oxe{L8uDAWUzS|4iE}!?y zyT{kLA(O-f%fg0$w2Th#2#_C!6NTOUk#i5%Ebs%N2&~T&W9YUA^#e&ZKB-+pA@A?R z?{0Tbn>w9KJL8M>Qy%qzhAJTcM<2(&BDwK2+Eq%Bts81YHY8R8J!`#!6oFq06+G(& z%~a|Gu3|2r7?j3ERuHf>F4)DV>EOa@TqBPv^mU)qY*96<>iIqd4!Qkl&Ym3qy?nIe zuo)vT?=Ko}(!BU@5k6HYGWo;1O4Uf0P=as+P{zJ(QCRRefj1J-+BDH8Evi?%Zx(lT z@`Qeh;^UC+LiXC1m=&;_tNxjIHQCuYIXs2R>af=YRs(xL2;SUApsS5SX?>3>24qzs zHQED0-9l!?54X0qyxzPSyq@2m`h4i%S;vsekB4L7Q-Jfn%#R*dH*AlkW1a?GJHg(q z0y|QY_2jvcx)CCAKp{~_$9>mcdVC&&=xkBAwy+;K%sHC-^6HuaTt)^ArAIX}RxBw? z!50iC`HTq9R0fnpP?rEL7h3q4Ut0RA5@2zksr{P5GvDp-w^!+5!sKco$d#DC^S z?3I9Zzr0@Wj81IQI#t==;*JAl4Id}xf6`<%HNc{VZ)ua*2)g2G5ccdWEPFO-cVX7u z)m?azt#M>8VL0EC+85FR>pC-$1hoa4cO!6;r>yFyU=mI%z^tiUFY#M3*}fK>eu310 z>aw-oa#=w&bxx)f@Y=*9l?@1}imcLvRni#qgJ_Vsi(x^2YU2Rog(q+|unX0Ct}BJD z)~kZlo3e5EYLZGe`aJ#+>2~bqb#L-`m(8jbNvcPI zvMH%n-JxAO04-P?s(Sz{b7^GV9Zruelc^_j*;k9&WItHFC)EKf(Ro}0xMCrgveSgC zqu|Y9mqxcHSuZL$YKfs<1K7$%s**<0gqCHsN5%Fb7|>&_^58?;l>B_SfOTvs{h9`T zO*PO7y@YU}$YquU^)9sLuR}$a<=?0>JfTpY1VR^u4<;{Og`p8i+dW!Gs>;rk7&bU% zAer+;H8gz$j=s6RR^tSXrLhl91?dvRq5o@mp(_o7*72or6^WH2J+%Zx zmK2>y&g(rL@=qmTZ8wW8R-e43qR3{~{Opsye%a=e_Hf>}5eqnCTTWJ0-Le_(egLOb zxyfjStqAD!2vn|Sw^nr0(MmFsE>ho>P}aCrTdk@zHLu^!CSCb?<>L!?O(UN7b2EK` zyP&L$Twk3nH2qWO*Uh-dmiMUO@5P~arEdXOC_UA$whhu1{NYBuKjEXvDbF`U0&g0A z0(Y1-O1F9q_QfoP)9f0+OsY5Cm?()&cBtE+Q`(vop^R4su{BTh)?WbNi1obE#dGDy z*c-%a$vQQ;p`~&&5xGGsWZOOKfFllVq$0J1kX0e&NoQ^L-6^sqd%>{u*Qj?M7azW9b*2 zbw7pG6(wacnOWWgD>R;&uWwpBW}aliasqm0uHhlXB+Zz&W$(4!n!*j+0g*3#cB9fV^>$X z0)sMWnv@^UG&}_V4CoBjL7rXLa?x{wEM5^R%so`+R zzH?HmhKNg0T}aDEc1$^ZxF1*`#lGZHc9(L-^1i(qMQ-(E2Ia}SqjgAHFCgJZK+bBs zCh5c1!-BSXIj99*R%I3WaF#v8eWvp+8ukocUQcmsx{TWq1{$GNMjd}^m7p1#>1yQ8 zv(a>Ul6uZaeQm;#QX~jU=^$1Q+4FV|S%3*^LEEf+I(MO4{ghr4hql(Vncr&2TpCR= zt%9X765?2HBO4Gi}dy;Da&5)9&jw0bnRFq-k)yy(g?YjfA<^aUcZsq$24iZ7FxD|4o<_SEY3 ztAEzxk)^0|4(_I-*A=^T5y}_zfbpxU9ABO9YdZc*dTNz)!O-le@xcj9T?Poo zH$9gSnxA=x_Ty95L~(l)bnT4oj6NwUHQ(>qaN~MHUKnw2u<}RpjBNrX>oCjgB39M_ z(`NoW3TtJGl@pD0TT^R>P`jyJd;F#vN^-W6L+XJ`a`=FQSR=zB>TOX{VAZ*EOKCb9 z!;*yYlS1G->;9)J#o#@pU~V#A!uW`lYOc*bpYLx{ygBe8V(0`1i|S1!YTWD~(3>#2 z-6#)-8Mws!Gu%6Vhgz;Ug23R#A96k&mkx-%`Y|cQ$8UDN}R>RR) zrWRxz*qI*H3Ef*V+mDs5(_U^OkaF_UROQ4K&@xrN~60?ah<}=Io|#wr>}7{%cjLY zj)>Jc9R(NFmI`()Yzp*tRT{7pV1q-(ybXG4ukX7-YlpWzpZJ1Q5gIhVzV~@K=t8;L zAm><6mbrJ=W=IXUm#k$P%TD@JfiNaszcbLIA*p5!tJb7+-UsZi*Nqzs> zx4WyD6>~uUzT2Dn6gM@$eVKj7+%-BW&N*YZl4+;Kcf{KAtvMn3RW?N>vIG3q0z;Vd zoL5$3EVj__{ko7vC?2^0XVS@Ji#hXXQ`w+D^45^RJjRe<8zoGi1{`T}77G`KDE&YP z@pDm^Z43h6?wprE2(6%3pe$E;n#8G_Nj3r53HfYBjXl8`NSM6{HzNTj9X1lwJnqll z*x?lS*}VEDQ2weWXSl?r%cPYiV`1weSuGaC~9&pAR4x7cUsxIj(bW+(ha{8Q%lUSULoW-1a;TJvpvr7&t*s5W;&!MjO@r;wW|xMal?G+{~DM zNtCt?g7+y!iiz$hOmz%4Wb2dXv4~}sl1PSQ5tDN&22zI~3)Sf)j*QU7&u6A>efbF~ z$CiSjNnE%2W{j~fhYzE{N2M-K9|oI`C~JT4*05NRqNn&~fXcGO=M`=*2?#^;UH-vi*xp$$hEJ zUV1@G{ys&<`wiX#=9QUp5AN5#BJmWYVMdB$?x8d!Oo7LkJDFYp41>=DV>>DE)yzs? zr2pGf0s16W1ZNg9$7dEuaaB@O89h!^`(QClbvx{BJL!cYp>o(!5|J*{GQ?3I+Qf{m z`=Kp-{A}^+?;UNoY!?d(x4auIuY4|kubeQR@y_|JFqL-j&(-@%jN|dEBFicD0 z9xzT`ah_KxAYb|hd5s{OgFjpAI54QRQ|(^+$`Q+%ZDU)!N}fXB860&WYX6nak5Q`v zLlP5G#eB|~B`XetPUy5~y2Upe+~-ffP@ zk=vsksX}>q4&TN^Of;@AVH*&=(w$$&N{Zh|#d$rBd>iU9O4%X`eV{ zLv!rR;+a@oO9r-fPG%<$%n3BeYaqo|*16%te(r$r(PiL1Ht_OzKb!~K_>)YzuC#uM zvWJhqawFWer5v>B((c_%^1K&1`jXH^6GtWp|MusYo9wQ>~XZH03%fpnx(Fa&Fr6i8d&W=>7=!BcB zVLO4ho=DA^%e?RZ%e#fd4{zLC5whP|$6 z*HB>{x|`2nd8la~+&a3J?D9!fuTWPk+=T7zOyN)r3-vYnYw3btiC)4M&sY11aN zxUTtpopwV6zr6)6|D&z;O9X!Lu{`f#-)v5thKe22ZBofN%3^tZ^{JP(H*L2~x~eC4 z;28Fch~1c;*2WfFJa+E`HkWSNw_&EtnzgmNfQQ#+elUJ+uNqGLm-NxLw0c*>Nb534 zc9B-=n7HCG5V|__yErLrDLH z&Hf?me*^gcpb-?^tPO0f{->k?>!0lRe~|`E|47pRCqVvhc1e?f^&ffi|Frj?ea*i~ zgMY;EcZC1zih%Wx%lN~v{~gVLfbBox;I|@R`tOPMKg*Z@HAV1m+T?FL_OGhqPgs8x z!9UvlUkiZ`1H$s38UYTbe>VcGAJG5bi~s`z1Jj?1{$T`||AP_uO9Qad|2gI_4M6{w z1NdE%fR%&kKR5uU|4t9F{<8r3qeA{qIe`B+$p2rG>3BwX>ULjeBft$kuJb&SxgKyNi*mdB6Yk#p@%)$fs{uG_ z8?_PTyh^GfDyk95;_#A?6u`#o`F@1K`?IyYD}vzjFJ0YEAo(i^6xeqVc}PtGeHuW; z&e>23z^3cf_5F<>wS8*9g*I(FR^e=!B-MMK)x4d&FutV1`$DS^ePtDg#dd?!BSlx#&tdbytwav@p3~_NJ zPIo2k;xML5b>nk~>55Yno$hW@1tn0-vM13ByY|`z?F2G*H%3w8utOfBaquYU*Ce+K zzwvfa^E66|Z7I#_fv)Gb(7E+U^u>H5XpH_jH8qvPJDBnf13#ctf=qO9>#5HVvcE9y zuz|9O)(DKyN$u}HaQ4u$#hMQp=`(zb_hfewj zNSPUq$qNyZA~m1@#ugzXJor{1c*U$;*XC-K7<{~kCy}brTR7b>?_OTSs#8#|7Qn9E zR+LP-DLSFXu-K+&46r8r$s;}Tw8M-%(Xy5u?rNL9YOmICwNJ=((T2p|2UYO#VxvIo z;=~=|wEZLGK;s1@Ce~cMmO-+-79f@V^3j8+h#6!JTKHhfI7jEJ)!Fr7&hT}prPC}) zZ@x}f^RYaU^Sv(Nk;rCQs;8fuD`Sy~+Du;_3Qh!enn(8_fc!egI3rr{w&`@gv%KCR z?L4d(u3(=jP>uHCA(SR`D~jl9Oh*E24eN9XkBN5UMJh>I-Bz$ie7Z32t@nl-QjhiC ztgc~mu`0Q%u{p(Eo$r0E(i8L%ocOiLhfd>y-sCCi14G!&0-1?_4~oTi9-MVBfox{g z3Qoy|G^$MyP<9Mb)p9TyU~|sw;R4J$uDt~@7mMh;z+5`o242uSQBXaQXJ&^#Uv19L zoW-OB%H;IgGR2B~BPJhp1>WVtlzbs)bi*7ukH*rn298Bv zYgy=H=tXFP8Yza-SMod55*Nvfqz#r;dP0QTY;*ctwgz`8r-p)X26~NZKklWC(A^6Y zrS-?JJfg30B*$rG)nXF5|*jlxbwzR@u;=fbK&vW?E{86}(1=--}#e?CQH zq4UJz52rss(Zb&E>MmN`I(sQrdG}M_yN*=y6eQ_SUu_xO2H8B?#h^c&pnu=ac&8oq ze}!m^?Bc8D$^U$Ht?_X+u=_%Xg{PFSl+XJ;10^=_-JTFp8*2_?b{u*l&bAHsu{#Uf z_fGTDoED-9PsaNv6Z3s5`Fr%d!XTb(3o|+#+=2=vK|%`h@V2Tdf9XQHWBHF3QNyn+!{sd2ry=cRsS9ALcYy>Gx3d?{R+;xJYcgdfMWCT%;;Mn_@W z7MgawH^*9?@vMLHR`%+Oj+at9ot@QsVx9p(k^%y`;varRtTd5ZoyzfXj}V!U2{VUDKM@s4fkUdQ9Q9>@Gy%A$xVPorC)%oI_&F`b#oTg;@Lg z7lj|@JXCwq%4@7${>~&B=f%S<`rf2JjR;#+ghbVcL>VJ$C14LU>UbwjuqFUWY*iMY zae;2~HgDp%eTu=vuAhFmA@H(J@>Y*SuzBGx>Y4L!*%CbqMr#Otr1MG=GLkzp)J>f^ zm1xjX-POm^P~CZvbXDi&CC{K$<;Ah|MVQ0T?`~3x$kUY5evAEh@gzaV!m_}Go5sT8 zP%J{90Xi3#UbQ02R}*q|sZ0nm`nYyrVCI@A1BYp->MnbZ5o}8dx_g;24s}_httG zS=Y0ok4Cfn3fN!~-7)(Nw1Hco{W0{dWEf;uKWfTj0jGO>=`!FUp=2jb$z))`R@_8| z6N!B3vez`RgDzVBwK1L zHTsxry?2XWjrl@*3NcOJ1MiBqcJNhFg+8EIec(H&Wv`$vavR?o&uAnS;<`jso$Cm)fZ|`fltYUp z1+^a%03PHo#|@((T_rt2hQ$5hH0pNVF1O#mkBkQkpXD{ zrM_uz@=?^tYbcH9BVy2s_Rz(wMd0`;P1TFAI-941$hjGzN{khB2`%r_{ovU#j!@ z(wYt3$8s0wH=s%XSY=*?Tp}{zBDOnL$LbIBbB2=E$yGZJ#{i&EsDMk@yg*iC1gfoa zx%=(ReX)*YUXonIy8t(<%`dEnIciQie5Ng1=cAXwvHV)I{4w19+ixA%A|7ImG4vCO z6LAWT&|0!l$ROCt+=BVDv4ryjDt-e2<=sj|_hLyZqj<|)DwMR%Ume%SMKD9Op#((RJ>`grdP#NoJLze1>6VLnGS_M+Y2^P@ zu2aB6XEqaY#1aef&pjg5fTP4E*$*f*M1`hwr7W@F_szAMIKxSa-&Oi0iYO-O!Ak~+)9}wda?+H(q)qI1=iYjv zCYw+?fHLEXPcwHyi#O9<*Me59aVS_8WKe5iZvtplETil=s(u*i_pBYfVSVu!#sTqBu zk9n;amt>QGm=d>BT4Dy`ec(h(Jb+CmFaRhSbR*0>?tgL zjqxtqNwV`%>tzg!>YXnP!3mpL_5~Bra6%DV6z%dBLA)yWK+54fMnPGfPMUU)m7=7u zf=kGVI`U0|3PzH>#~JP5uI>fgFU~IgCyrm-#!>tUW#$p5s9s?RDJBL~N&DnyjFG)= z(xm7e%nfJudgo;4v=C$UqFuvQW_lzU&n#m4cn)nzXqOR2oD;i^6B?<+AkxfMP8j4z zH9BX*KFqTEmO8h&#>haM)KFDScxQHiGaA`w-?vE5^VH#?Z<*LX9ZGQ-R%j_p&LJU9 zYjjMz1a>zLIzqsB2_Qk4*QH?(YbSfN+|?Kn))n!r$$0%~X?jP{0ik#ezx0rzTd~W#L{mf~kYe^j9iB~o?jPCdw5vXYrAFF}&vGZh2u!dxRgZU5 zOa6Smo_qRQ^V%bpW0|f+~F0VP+5bR^f9pe4*ESS}Tv+`W0MqE9F#yv>aKGdO2 zC8R**q+IDqc2XcN`|eAdBdHjhF4k2mHr4JOikSJS)PQ7I_6Y)l#SsOrpVQee(YDVD>4~IN<}^>PKyrFYspL#fx_HAZHLty z>+(7xeV3z?itZm`ec+k+VOEoOc=kk*sQ&#)vH^0I0sMm?Gmv$C>X#iKE^&i zY+-|=*-&vRDK>MVZ;wt@fo8=t8Zq~qj7FBMk3n&KFmOr2CV^=&QX@F~sIc#w!{umt zoU4oQjNF&ZlPmkvS438;oSsfEr1fZbK%lroCZ zN&^G9QE`f>@|`tJov4dy^n?b~=LlLk%vnx^R#jMV+h%i2e5jhJN z-G>GS-AN@fMhok{mFyX^#IR;@D9oUnh-`3mkd7Shx{kG4FO0Jn;bYfh+Ag7;^L1iYl^}_mv#)4YnJ26*#ZS3-V`=_Mkh3+SxW88zUtEp=%Cd zlbCX0##|t~#a`n&x&u=cP)6_H_$z7QSnOw~WY9L*h7N@WSNKSLMk4hsD?c~tY)3bj zo9}-;5E%}qL}#@!erCvSihBNe6a(rShFvbZy7*OzdNFAvN@1KbprULr^YosT-#OCs z=9ck@bo2^uI}vI^5twEhTJjl1);Q!9sV95QtQ`LS?4&_@ePseG*QKK|LmmR>>|oO5 z{3$_Go8^8ffg+D*ulUM5*LrqVp;N zP&@8@2sM0&U;%WKjfK1sh0jT054wG+^wbxIrf38%Q5CB4%Q?|BgX6+NB?(0_(tsl2 z-r6E(c)9cf6v6$XJzx)@LyG6iWkXbxc+O$hEajgx8sAgYxRo8#V@hU&w9rP51OsE} zSvG|Hoak7*z<)+zCdH6dV1k`zCo7un7ey;>Z-@IC#>q6$79GWqcES%{&$}yV95GLH z;Y~p_JL$-|G$8X72pk4n+Zm*D5r;#L0A+10D!9wc&CPXEU)u?+>|?C-eZR8npHgdw z6!l0D5jCJ)Ea2`$aX1)}uEvI5$2C~Tp@a^2$(k@*6``t-Vniz;Fr=h*9? z7;^?Z>r|Z@vCW3RLAuUuX2ML|6Vg}J|5DB3&$Z;Th)0lPhPNF_xnMLRIg`-!aDBN} z4=i29nSO3o--tpjCWLKFG+v=Z*?2>_>8v~74mh5yjJ(Sm%!kfRpA91K^%obT8o5NN zkz*HH;wNVZ=yvX8t_{xzmhi5|ivoSwF(r9NfNUE5@qg(D*nVfq|BD~^KgDhTpXP4= zy&MnQpY&`3wm&K1AH4d1DyN(6ujPUM_mEw-e~6-a z4##=3ZN2*;+!KId$f&-b4|KDOC(?O@1~@bJXxBo)J8ocJY@OB_#%%BHe~?`k@iNM| zbqmF)KK(i-thmZIwO3_l=eqFfYM*i&n`&KKZ!4<16Xix5R#c@6oz5161=Nbx@e$eSEy!K0jlE()_}^+3|Y&mU43Vb2TRC&E4xcM)rP@cfG^Iv(3Z% zwUnppU9-*aqRP{KLtjk%10GQGieLB0kydAI8#cQnBQo0GiLY$Qf?vLl%M+T;ecKNNoJc zdrSO9LV$K3@m+*cDPQa(p&M-uS`bD!h>>qq1O@_Lz@2zV@^?Zv+tvGn^zD3FET8i1 z1SF@hz4mqZLhywb|7i3geohXrcWA~pj4lX$OES^(o|q6eBE$rd$!)8Gn0T~b(M4of zc;|#S3NKGCF0vQp1Fv%nId3i8Ei`}<)B+F&_B;MbJhQW#&zIBzDY?-yew~>1N%^3V z#?xX(c!=tsLhDNSyw!Ee1{RBKcwc@h(00<)obIT;9Kd%mM#PL!(?{4!(xK%XCEmL{ zfa$(~$UbcQ%s^ZYbWJb5CGc)b?)*>+(*!ZJpm*c^QG{W-)M6g#M^B2G0@9ysqy@QG zvY({+((?V|e@P98rWVqJEd;p;UOh0?n;Ke21Gs3%&L6>P9~s|G+m?-B6&cfXlT9J=qC5-<3gsGCu<4OD zbOKe9U~WjFhBtv<%$j5DuTBYD=;0;zT$3j81h$P3T6mZWGqpTlxy}+hy&~Wt?eBou z8ZkCFtYUE$%&w}xZ)+N?Q|qxwonx3S(37+EXLcM4Hvv~_ANlPMR*8Wp!1-#}wrZcT zTPHCObK!@m_YONrs%k+$bI^jq0oQ{W+iz*+6u(m;(&(o&C-x$ z6H`Q7)(lIAfZepb1F8dpE(#HJ97M%+Zk9gGg=}wfGty|Q(3r+phG>XEA~?Yaf-NCe zeHI{rm`0QkJrf(0Kgu!yZ5k%!Dx!7Qh~%NqW<@P`k$&G#>?P`KB_hXpR&bD~mnoa+ zY6SWuQNaRXP^15_>aH7p*}p;Fo|S}MXOk9YW!+!`$EaqF)rN-EDX>{CsBq)k1J!`H zO2Dxbne7x((3z}d;K~bCCF?#IP-Co)v|p$QW2(Tebyz&`dO5c~$G>Cb$$Zcu&UybF zD!=%JlaEA;bAf(7JVbDMu~Nm1J!dn>=e38o=IQKAfkp>V<4fz^tQ6b1EUs$TFb8An zKogEBAsr&J_1}oQ%iu_sEkV#?l~`3`W@ct)W@e@mmslz>Gcz-DiJ2Kv%*@QJ)xF!V z-)rlh*_pMrnLinsuE;b`Mf~tQ=lFMo@_pxs7k;@Gyph`AJ4L>VYSwhVX?BRLd%j)y z&f45f0O(LE4YDtRxY%Ofv@JQB=vfL2`nPk%Yd)fo#P>g!X{4<6A$jW?q2IFyYoNqRSZML>+VwDHg;hO;PSepic~7L1)Xl#Dk_ zURG!tKCif2%%+ETT<}@?!4G*cB8xk0%nJY=^=J)(2LxLRnXS!WWP67yD3R!Zvz6#O z_2xQCOOWtDG`)$tN)|+={uq>8*cw!tmzGXd{Ib!G&AFQ20FER6=fqbcNwzA$w{kf_ z3#BnuWe7KJ6IQ;Tbik-jT%5|;9M4)^BC$lE>IW_qJ^1D=*$N!!au_9@&n25I$X!31 z>4QMlyO|<*v81Y=eRwC9wlhWMi-^KMLhA`KCDf#5BtFJKKI$SQ53lu+0y%hN+=%AC zB$tI+c^NYj_5e8?SRXwxfiZS-qc_?^WEPvAyb`iV2QU`;H7PiOp+kliM!+7_oQPcb zP_dj|c0QquHp0;-90e<)`))i=q!vInx^gMV&sBm;tI^7wi)s-)pRM_;3kVeY?4!hw zCBqmS=k(tXBxl5{+b)KcFPEP3)icg_k~0tlWv3bXy80!>6Y^YmUA)C}@x$4YSH~$x z_gkzwbrAFYl^_<2xs-2FpW-;RtIR(Q^LiHYa2h5+HaiEV{T0D{b?u1;+&5;qXSNzN zK+Ms-9=J|Fnl-9{|Fn^(V5{}^%wY!cL+$599j_6;K^<1G5++Ex@hr(-L^0wi*3)HkE14C2sa;dO9NIO+8U-zFQfLf^f~VJpevstvm9So!U{r)ZOb5V8 zOkP7g1)Y`3xhfWks85n>ggT#vH2h-vMf57evg|q($)n-nmg%RsjLw7s9Jx#`0_yJ( z_9_$~;X^z+8kZuQA#P=9^AeGj7(@{z)rL6BC}29EdUJIqY}qhWByeBWQJR}>R1SBk zB4zLE;sI?YR%xu9$HAu@)ktqHoRtgtr(d?x^PW?#$GrrIj61XyzWoRfFZc zb^hIqE9-?D>$**9qt|^M|C0?w%~RVlV9IV+GkC*t|z<<^NZ+00Qpd}o2r8V(t;JX6@J$Z+!En8T-FXm58Qo>doS8$6Ukpf(J) z>}uYq?a$1QI#-;2m%JlN#+#V_u^*jC63N9__$}t|dmfYj1J2zZtwCciiiAieWYrodiJ=j?7 zgbdrukjSZdP+U*xb;EY$OD@~*^a+TDuHl!LGql~N=t1GhGhR&wAuB1;g#D*SrsxpX zZj+S_d&70w6UP3tbr#w(i5*S!d()aDIfCT( zuCGIYLtVgrb1vcUHb)^hXsL$g@f^fL>dEyy{=5+r(!iS>WczWRKtXaBxjXh17r3`D z-`jVymnS_M{}}Dg{{45a^S%#9LM!oKC7yKE0r{AN(lkk7m4T**Vox#Vim4;z+~g$r zr@#q(beWoK#mkEQ)?8QjXrTOR667LltW|{%>TKm?Yk_)m2cu68CRWK3HP=aDQYqMt z#TlC<>T`*Q0u(2sLgYHkF<{F50{J6QZeZjV4!a#VX!IO?Q8}LZg6TtMl-+sb$!vdo z!HZ&&pqc}7Us9zDQLeY~eareG<$qry`BEwBtn%sLtu%bzvWC`6=k;G}hH7I`CsKT8 z_dT?28p2Yg-A}B`iC`<$9;FJD=d31X0{p}uEs$8@)>70M(kXm}NXV&FmK5tbQr0R} z&e(8goKIiIiDEXB9leYegXr!G;2+VkB^}h*rZRvhTY9=z3VtVk?fd@r9jFH*U#K7| zdYa^YT*HUR!ocm<>ejYa8vO^Lnbjsm1-&->9dv)mUoXX`7lW!!C|krM1r)XcUbALE zaKRYm$Sj{lA742S5Uv@!t{?E;cmL!iy>J_es2WeGH>qpF_of}fQS z`QF2S?vc4d*y2cP*7|7L;4m`hzgo5;*u4KFv zg$m}2RGTJyTH+s5c(Xyg9Cr~punW!9-wnE8cZ}E_if}88ouW<2n(Mk@D;ALTs}DeB zNAgc>L5?AG-1WznP-?S_w{9{3U_~dYUZ?!(=97lGoq`|9f28HzGnfh13;SLK%Dh6S zW`zmx=iM>fN`jTf> zm&`ZWadlG#QA@{^$aYp;t?@ss(-D4!BOKB4QVYn3T}jHTYEV-qpyBZ@@2^+-`7_{^ z)b3yNL~gBWdD=$cajoD%)WVkn_R(D6*Y=MbT3Jr;0g7yDQR4w-Bru%qL5G8Ce++;9 zV97aVEe-)bhbGmm{D@a0*43b4wo5!%+hGWW&0*phdVT!hxf6Z2DgIus7Xig01L zuWKjgjrld1^Nnx6qVsP*;R*Vj>KqA(SSG1W3fMqv#$GxJ(LzBCAio*8o36=^x}8zvBi+QEWq zvdN5n`-C+uPET^;zJ)jAGk1DqMr3E93HG4z=rhRAaS%mA;NxX<%QzYb1<#EzC@zo44rTDT^d=1nA@yyWohicAkX zH_--H!RUl?q=`v9?Zt9{<1`XYiUP`uc=MiSua71+rANiCe%@$GkbP|sm-tykS)FEXi+l6O6hJ)PTg2X7N|NP!V9F1WxLlUVpBlS7?%J<=8UX0NnN z8v^XD@ov=U#4%|fjC?T8 zy}qZ~MWP%VzApY_T5hQ!27K@d=OYwIwheK{R5~|4I1@G`zB0-vY={1sniZ));?>AP zruh(E4(BSQEF0$Jqpw@MS}%qNqgF%ttwF+7KThhcPr}t8PO1=o@+8ph@nr8a#uhc4 z>?7)=NrC|eSNL&f?{mZ`sA8|2O9ZrUMl#R=fG6-4shc1}IW17G33gHrD43r%^J^F` z=xf+*obsS8acn6_#sy)rso`rzUM}DE+QI0vF95v!`H#KKktCqX>EJ=pWL$_%Q#7Mt30#3 z$J;K!Vyk2=E^+ZUk3+?f@^_?9D@Rq&v&(6xfL%@8DpTr9ury$AK|9E83Y=jJ!$JF{Hn1`>(6TaoVH01kSie9A)~~^@=-vOe zE#u-s>*&bJOlxFoO=oD}XioQEXfc1+Q2wJb`tLyDKLT|BAoR1^Q- zkJkRHvd-`Yg#24+@P9kM@x>7RrPTcQglhjL(Et5T_}4o0|8c06g`W9872#UjVskir zd==s5+JBp53RX%VaM1-L0Pv(4`>*!V$_5i(;ev=zdB6?V5k=Q}1dDtL9K#nF57KNL zJdEGLf(q>G+n#FFsZqt^4G6Fl%3kHmWAO4U2K2;{zt49Q~^p&$OOCE+Uaq7KYVf1v)Rj~y6k@F zueU$FC%h|dKJNF!t>!$Z zdHJTl@N|ffyU&9-soWH%#h>}GJ^(d*T9OI@ek=V-HNmTc6Zs2;YDrZl+%X>Dh=e*- zXCMgGbQYyQg_7SFL>+$9q?iM(SPi7H!U+3TIsF*AkcDS~wcRff5*-D5>#l!F4A2}U z_)z@{)&6B~e1&TJzK21Z_!wb{vD+ceA~<;V8&*cW3VhghmE*P%5X&ddp$jyTS6S)S zu3Tl(4M*-C>^i?*3%QerU$cJGV&a&E(8FT|8*3C!XNtOrU+9A@QCSOFlDS^=Zujz3 zqq)GN`?xvW-!O9%z~JC2Xaoou^>voP@&;IP4!*pHG$E7do9jyPB85-`_lFtb+a^0- ze{Ad0(#_@R>Rt^zB<}}m|73bnhVlfe`@>mXavA(5zwM+ir$#x|J|c($E+5*Ay$uV= ztMbUJj;##?-C)QF3iWU8R#-2l0nuk~AOyx3M>4@w! z+6KLnK<&3NiRrlH%f%36FhJRa*`2E;`n#y76!khh*eHW<*geRmc3op@XRBSZVm;iu z$`1NQ#(trSvX87)wW_YxGJwci*qQyYJ{8d(QmDjlR~Q&oE8u8k+uU#{o*LiOXu4pk=FO~F2Y{F6*JVI z5`n>LH|T4L#*{>h2Nv+mLLU3g`QY@XGDlpXq!0QyS=n2+beYY6E7BLqF0{m9sv=0- zZVX}uuT(V#CB`y>^QAQR#QL%1J5ggiLLIoE&^GG~OHbm-7d!G5(kuqhQu+$DL*zr; zEkI~bIBk9(U&{9x#O4Xj3P8vn2g1tK_y6TYjN>06bnGJ4^3>dJ7mgt1%ZHEJ6g>u0 z`xN_IC3#*8dVhm&`t#yiRnPhKvQ2IWGt-vr1+7BQZNmKi=qH8?J$uH@ICd=O$iTh zS_*W><4|>^HRUV8I7UBGe>6`;Ep4S}xarz&VbQG#hk5dYF1_5u`5%WZ;;l1W{5M1* zsd`DAo=?Svnx4A4O9OJQ`>1pnm@E3Id~_}9o_SihHrF6}Z*aCxeV@w6Ss$P}581r= zkWb>D9(e*$;aHa-CH+GUPxx9--Kv)$TJe!q&wGM<9IabV=q^kiCJr<=+Egs2hiEtC zq-cbCeUfGbsoD;na8!}8TM>l*Cf449@h#qY(Vr4shJ9rn^O?p+at+$rhGYC9h36HnjIOjC@j&(s-yDAchWrCzS06ARu^z&q7GI2 z$k!_!`{Rl#wNEY}nwFNU!%qCHTq`9H8=S6>L5|EdBF=mcne^mW1epdW z`qA>JVzOi6e#>2ds9fr*!J*VQLM{n0ejAezkpd~6Wx{8&bD+<6d#6Har@FxBMi>_7 zd&^Bs0(uA>=ct#M44ZLSTBf{J5m6zK86x=CO9BcEc7&J=3(i*Q>9y2JYL!Ijw8Vo( z)Z#pH4Cbk?m8>}~{;@P!aB&EqvpkHO&mm!O!PnE-u6$hlXE&%C*RH~*o;!#UGbKoh z1=wlE-x?_^OR1DpJIMkd=Tq4D&+*=we+UP>JUSdj*<4j{7R z?T?}iaffdCj2CUUqV=MBwKbZ}fLrK4=IT5!5;HZNqBnR_#i)-hx}-BWB28Ev&QT_SZ{X~V#B5$m(-<{=n_ij5`7TL`4Mo0 zfT#Z^vm~1^a^Mpxc-Rtnywk7!V@aQH3hmC*GuRP%kK99S=xm||!>AshR|EOn;Xd@h z_^n0_VcO5N1JX86Q9RiAsxFKjy)qZ`QO1(R9;vpdeAN92V+Gu^6a13}Bfd@7f8UM?vC!_{L!9j zy7b;#`%AY}R&umk+Y)2;mWf)}341#|&RUb=HF<={CCm(v`ZTC+G+uEYv$lmv1u@Bj zqRoyfbU>>bzch}ML@Ton=Xe#_I_*Jn)RiCQJbt&P*Xb_osk}4JHipigFEVk4-L9J9 zTiTIIqS`$sa@eEszCWmw@{8f}F zXL*q@f^__F@W(_}x@U7AQst*jwkn0lFUz?U6}Cb>)r0T|Lqi6Prl_v5W2D~TR?v_< zh~fLGvh`mQH{OSYe;_Y0NK`pX$4%p9cWnU>R^2^5VeZFSS6Oy{^&Fn~TZy*USG?m2 zUjY#jv@2I788QYWrvWGpD4)*5ka{i)-a5GzfamE@GVSYj$+b8fKtuL!l?DR%kc+S6 zKtnFjD+$$j4QY#oFMk|*5y(bu01GD(koVjZ9;3q#1j@5=+)nIBI3xEoCt)8@D=3~C zrQ+G0fkZh>qS_v>2Mo{0ZOiVw$%Z%jI*0thi4XU1Xa63a zU_ZN;;EcUxXcOhkZcNYRNIs+V!Ek0{zudeXIl3i>SRd`ScXSl&jIQt#kGEJW4&&W? zvXMLHeAml#KP>`7S!f8fmP$XFAwMfE%_cA7N{_v4yeeNoqZ5Jx9EZGnPPDL)6!lsx z%rP@Q#B$upbAiOp-Gjv(G?O4^QPhyVEnl;lw=A$$w7_Z@+aDMNEew*8M5^uCUUv=I zk|K&yKT=v1Umc>Mp%G(Lcpu#~;<03$l5b%+{l$?f&S^r!)uE?!6r+eqmIte1A%G|D z`HF*xCEas(cHXokUm%B2TxD|-X1**bAXqx0KCy_q<^Fg{0BM#I;}8Ro z0_xGdlX(4&9YnOBY4&*GS{cmowC)e4a=XcBL$9j(@9Xi~IeDxZa>8U+f!fvjL+WC< zYnh_LhgV?IoIJ^6wci8`mn`mmH-0Wkdb2BJI~VoSfJs+yJ(6Ra?M;!#Woq6DWT}U& z8%Rm~wCpvsi`#wufF(mJ{0bcbX0TUi5`!A2$LX#S9G9P!4>rEDe@5w(XOm9x?uUlu zQ`@it;T_`0nAoFKRR=d7h^j5*&S*&&F)==t$^qy7sgr876DXdQDd1&-C7=_R`6g+? z3;sBgg#?JiaGrJ}Z|y&nCO+0-IwUy0$pR%#`0deLTu=-r!6qZY{by|Noi&`fqx^uo zpc%CwAJO!Lx?)rV$P$g@_QDQwu_vGT z$^LYvyLK+m#}_V$bm6=2lM?uCSVgBC+ezO6BPNZ%1{lnpt|^j|JH}2ip}CM4t$PIjuJt1b0=t5Uswqq<%1uPjAQ&z2W-J&GE^?q)SF>Xgdz1 zP2wge^BH|l)=5Gz>C9Wa@A7JF;5bfgng~Nzt37%rMeY1iX}tAj6Jm_xsLM%JYMCF* z5`~w;RzZ|bkP|s`L>JX~7hOFY$8-$Dsiq09{7aD4HRF!C@CWEGhkNYw5mTT9hTsxM z#@pZphX(6fCL{|XO=tf;?~a2#5c1tMI)N7h@k;{FvIP#A)LLFIsWHyw8E9v;LYoU}qiDGe7>$_YT-VgkF6x3bADO(rewASV2*L%Oj^>yV>XS!+M%#9_fz3Btf3aOFf$BCA3B8w*&M3(v7ejJ z*Ggq3w@jhi!NL?C<#}g_uJ19_VPu7)^^<`Ws(N6r$FkqkQxDf@O$yCaK9~|pRQF8@ z%~n1@h!-(JaCjciKf65vaaUUx70puJ-O@k?PnASQ=ZjZQW}9)`#uT9KP1R?hj}&h5 z{P7~pcN@-=w5{PnK(VH7f-I?fJ!Mo*VxmcYJ_rB)V@1~|J`kbe<98xxqCsf}R|Yx{ z%ao@!h1mGJ0LIq}^El*d{dpYnC_b#+z?LLn#{yp(aM?&2QRp^GvG36nK?gFVkZ{-W zwYt6Q@F3IvJ4%VWyU3B78ZX7Ti z3_P>b8T+|gm_*`y2OVjIy#dXx$c=`hZPBr2Z-_|azpC%xd7qF5w8`#bqHWStK0sz% z4NP=xmEAwiyEFhjGrjw5swJ>jpWcZwXPH6z0Kd4$3QS<69$y;V=-oP(NOVmweLG~S za=s3JklvpErDBw^nGa2UyFBYf((!%A56tiTOGj^I>_>S-0qRHjJR^i_FB=_Hwso{t zvVzV>da{q>OHc=I>>$dFa%k>~w+x2KEd|#G*>XVe+sv#1# z^-SJxlPs}i+o`w(+bPBEbuuLo?`b-v6Kw;$*~deT_DME1?4#;_sV%(i@^Tm<@6=CQ zqE|kNhI&8v3#3wpoeW0VW%gs^30dUiV!WB;fQOa4^yBLLw2Jmmht3ApgLY&5I=W1* z+UhH2qQlgtq;3lSu+^D#SIpN8e8QiB(YfAlb?|5Ms_a5DLO^lzm> zl!5Vou5SOoj`KeW4K)iJ0UJk)e|Nb5BmeNFPy8L#6t!@0{3T>=;P4j(Ep6~`&lwmQ z>HapI9sg1){u5;V_j~^l!T$G&|I@hmr!Dbc@zeh{(f`t$pkZL8{Q}+pzY%JVue1Ig zp{8g27jlA~iQ#`nPW)$t`d{@48s;yW{V(wPtEVT^UjPO3U!6DqzIuOuU$*~~+QdIw zhyJs%=s%e0|6{@L|4xB|<3Ic>|JMWxRwjnOGZFt%r_7_{xW*oJSvu-{6zp-dfkX9{ zP(qV@`tqd;rJs2kpPW!KA>a<}J}uOCTue?*jJ-dAiYv&JQV5=?ZqA=Cuv^B02q1#- z#{iMpcBX6VcDglrwx!jSmyZ}WW?YS|pQ@q#bm(EsC2O5aokhwQ9=zMzf4DE-{J49W zH-5L_NCm^YJ=yhc|2<$r8j(T&#FvbuVd|-vK*$uIPFJnh;r6=!a-k}xDi?oAMQ?jR z*Y>(uW$XOyXXmQUrf26n^Yha|AK;eGmiO)2^Yx(ZwC#21bp0J*G(Wln%D-|Y!SQ^B z#eB4qMh+E;Yy$7i$FBtY+C#e8`TO3UGgW8rL+;x9`s%Z5Uyl^;*Pabva~{U8tljNleHrVJR`#LT^`P+wIk1G49?Pm3FM83L$+{z>tQJdvzW zs4-}TF%U~!x-93mV!Go}>RqoGQX|UQV9;x#jko)kYa7N6ulA?oAq~)mRJ$3&If+=U ztNCeTLjTUf?y-(vj{GX1=A_%+Ko~iUxq?SmkKkj8^v`=P*rFO$YHDcvB#UE-vyPEd z(%~djChTaP(fYZqoW~x6BB`OOmvP2`bz=ZS9k6!_>M~=NCB7UId^hUg%uj@u7}KAf zF;TLfw2Pu!OtY~Q%MSQ(FmXYFqsX?-ED}Nh86k}PT%g7i;Lp3$#i8{@;-@-b22tK9 zKSCe(k%XNjVn>D8Ai5OYC9~S;({f?Q47els;OEqE)8x3!?F?eRZx?yX*~jmFsuk4D^TvB!3vw&WKz3v%X*7!S+q#AiFO1zqGjO)zpjG9i*p5-3XQrtzVTdua~ z?!QwFoK)W+#5jGR3Y!#K5IR8iIl&k{2>PWm zwaGifx-23fT!l$#c{&OaU?m0?6Kc_ab*aru3k#cw{|*d0N=o>{jVoOzq|`whUJfrd z$ja0G6WF|X{T4z0=}j;fM`u4X zp0$$+b^{M`@)19JQHWkl%0bjAj~-70^paiJe9jL^0aZZg*e7_XQt@xC<><980~BX% zX$YhFaPs@Xo`j(&5I_J9kn;6s{i_mB8n$$hF9u9@hl^RGGBaa(zYO4y>SXinjtdeU zQ8emA`)BY+9IJR!vn=Q&l$Ht|U6}doh)VBOgFTm>F$Wvfx8K9)Q3`u!2u#U$qr*Y|Nf$!)g}rjafu>7`3*&9+-#B zS)x5oGL6I%>hc$Vbhr-GWa5^5p2MU)2Tqca*X8(?Y}iUJf$?l+mP?pV$}Kdf0z7(n z1~LS@b#r&-H1F3W&WmNdWA^$V0Hk0NQVSUw+q!i@cJXm6WM<9D6$HbQb)9Q|^zS!! za;GW-YVl#abd(sUOYf+kq=8nl9vuRo(7eX5FGqShsHqDb-S_id?*gb^_m@C10cIsX zstCHJMu=Y+<57(n`mvOu7J=0PW%E#r5X13-Rst8o8rv)Qs#?vOT=i1jKLqR0>!e8B zQd14TDU5_hzS-(KDc^yJP0CMwz6-=L&E0x`i%W_F=TM=dSoTRzhzrUnOHBCbBRp7P zvTa6%*VN>S4)J>Bz(iG)O*Y)bLU!qFUS>FuELM@6kQ&5X4^Cw#*Yz2a!kebjC)h;R z+?nPCH2l|t__~}a1Q4A)0Fuo!;2<#Wl#yj(B1Q(I3YzNZ#YisuZ2AVsIAOO+^Kc!0 zHSbSM>R{$EMBy9m24asngf-D2CsjBwGSxATvAzR-+A(BQMo`wst3PpZuGtMDF%7zu z1Fw4zdan%hU|dGDE`1Qbk>B+#;L`SLs$ON^MPH&QjRel1CqiYzpx(lYU>@6RQmB0V zioj^i3{PcOA^P{0xXwvS4WGDTq&w{yTpW`j$u&~+Rg3<)bZ42k#)`C`lf8E%x| zF?$Zeoi7!lgfyGM?1H6LI?XKoHXEwGOS)-&vd#A{WKV|dTMhtp(sIJn{?Cvu^yupx z15O{GfL@V7G>>@jpBE&T^(GU`h%#E=hAat9u+JYW5|uHS<}V_@r^e!VEMye2;nCzU z0)dGOKls9v``u8RJ`FkNu}fWY;rjb0d76$TCG6!gkkig4x56qqgG5T7>g`^78^)8!dH~czoS%?MB^Jtl=6MZQEmo<0q zve9n!w}u@vU%7O_itgRgzG0$C{3s5tZvUboi~t%mSJ@`tAxPys8LzAJh|}ZO?6;}H z_i*ZufcegG*Fx`0JWChQ-~o0Xad}h zeWN$}8}b{5{$Nxfvlze)Vl?oA$eZ?UXr6*eAclDA6Vy?>$Sam6#4zWr=cV*RO$bXXDHD|8dlBw8B01LV~+*4$eYtd;!0ek1gbI?z9fd*ud_ zd#xV$JN$SGfI$ts`V$y@>Lc;C5BcGFa`)~wf_VPvgg;@yiP3I&(&!JGNYYR{fj#!5 z*&l?yT~AYw5HZ_E;fxnv5t_iK>7D(*MuvdCM$W!Q;PPPYm0yt?k$0}4D2!yb(iaQ@ z*i{eZA!l(p@kBG3Nc67Vm zf=&j0gStZrHSig^C2N+e3hJa;b3J1OgjWTGr~UTdocqnX8a8kI>2^B-X2f-Mo}Igq z?O{>z^n3tdE?6s;ujNZVc`rYb@9Eq|c zB$@RTd#Avx`t=PWQTWDrzfKcPWjYFyGzxT8e>N7`^5?*7VhXsyLJsyK;FjZzPSePR15gJ0ZJy=Navr+%BmVZz$x>g4)^5iw|P7X_XxV@K!sG= z;Ha^uSv)?aZo-aa|Ldb<1?Fjb?!n|IUC=<+y9NGh`E&(l`49vOp&bE^eDj>ITN1N` zJ$TOr(;VGo5`Uix?(1sn>&SkATN=1p0kHf6)uQTKyCm_@?cVtw;a0KgDna8KIDK z#^u{=2m)ZSYG|W%T8INVu=q5}N1GqjTPw;J&uPb+LW=ZijwM{aM&Lum0{kouF zy-URJPtB2X(YlwZ;lNBcbGV*pRzxYgmwNp7N&%=VVJ|hIs^6S`M5VxQYvi&mj0R?D z2kZk1%nx&hwB+VnHSG9Y1%EV%`5sU*Te0jS_5p5m1AxoGaaxer;8v0{xx0?paQnum zOvS?UC``sMbcD9tcqwH#ReWvS1MCR{v5>JpBCIyeyjB=(rd;WxPt~SvuZ1j0ZDh`~J2-Fl|Mvu^(oiM zp{yy9^r7VHVj{#IQJmMmPs8+5(Y|LoBmSrHTscsKObNLK=v&4|32W zD*mTidN<{}&)(w&P_Ow17*^Q#KhiUqUlfU}CT7uM&$N96#Py7<_D+nGFxLPmf|a!m za3YS+$p@3?CMrMrpS4$ZWewb!y08ZARAYnFWCwU5#WZj|DwzKgJEX-+dlPGr&!Bb0+m-9 z?TNk0mg;=(Bs|!nL`3{CP!0W(Yia~nwbuYtnB|@o3S))y&)X%r8b)F?^ww7{IMzSM z%HH;#aL2J7gaPm|L?h%46KC9Z!c&BK;E0N&q?CMeW)3ez)aV1h+R}5g-~E{GoRHBs zsf!)&bUf?2F3sFuLcD(tpgmo<2Nyn_9M3E```}h*yVS3BcGYf?Zt85ly}!VFVKl|N zJZ{ef|*uk$h! zyGcxGLs6>XETa|;M6*1a2&>+gymo>|eS=s)!D=S}k$H2brO3{i>bq+Kmv^o_>ck~Y zGKTiw39pfwvj$pM8|X(nid!y>1wCIp7~v`r_A-ENVMXx4tC~ z_l!Td5ol8I-6l2m&jF)A+6cR$SHK$DLnP3_@DD6$?H@ZtR|;a#>F#>|Nc2;i?HJs| zG$+ZC-epo<{NrvPs;8%8PCc4S8N7-0gixy6A%qA6G!)mO82Gi#kG$tP<<}Bar+P&i zcaPdWA*1Yf_!t>@yTofjB(>nyd173>Rfg92M$&h&rq{eoyN90MWsjs|7#XnG(!$mm zVjy=6ZR5ODZB(a0l^mM5#^h7D2C=jl{~vA0Ic8&`Q=z(K8I6ztVf1Cciz10SU_t_R zU7qPGdpF#T(M2PKz;#BS;0Kh(F3Lh@`l@zxA5+E96TpxVQ{NfXvI%-9?Et8 z(beTvCng`Skwwp*2|@crDpOUggk@E2GAt?S)xM1_0drDJ9>Nfoxk>x7;;vjG3n9qr zFxc${HIW+*P@MkC?|lGe%mm$xRjx)%9Rki7qAvD6EJ7^$t}P27LW_KPWq5macogdw zuqsFI#G*o^gCCA9wuwa8b!nBrX9v)*F32=xY0V41b zg5t&GL6nWbZR3J(9>ZP1bkI&DO$u>)Y1Yx)=jd6661CH@^{UOD?{bGoLc&I8f>H_7 zv2}}I5;SeOVSYX=;yKvvsG`Y zL`0gTjc(3%qxT39r}J&4`zC=t@-88&hw4{{MYjDhDYNl9DI>E46gUoc(hBsOqz}BF z(fmB?jHL_BFBwJPI+Li>i)^p##gC5l0?L-nPR?e=>U0?;>V3gWA==+7__!-R6XTbY zuFbU3bj}*13>76@!3+jndV^v!v3-Ye^9gjxpjDZ{W<@x9RFJq1)0!m;!fac0pE zBft0+_p=G~g?cWv)Rs}tx;QXFtevz{2mlseQQowG|^CFse z;otRq>05oovlBDkos4%Vn3-b21I@ifO`Up!`OtU^NvR;+oNQhRo?m6Ee)w4*X<6;q zb+nMON)5d_!d0Q%RUy~KJ6_>js`ajSEFU)sh9*y7D`w}*;OgGq14YcF7;7`ckPo56 z>J0i58+?cO)SkTLg?Bd^@aVW^<#|~3tDuHpvJifV6ir@=g&{9gP?!T-47r5jp&~Yo z8rfa=s?YAOfopY^pMh{&i`5vLgYdDJy|AW+~NCD6sUfE!jp zFIUT7_h$4TZGoaONc&PKjdfkRJ4%%ukFoAt*mH+lWb=_T_hf^$W4`?bAd6AyIf5(c zdLDUpsLPSTIPEEQ(64+OeckVHPMHpL8#~`V#SPh7)#>F4`nNN103utWRxNjQVB z3;mX(lJkLsbPO9(yUnw0dt^1NBD^Xh@6kN|3=Uy6{vfg@)9?zcdly=dr7qcOAv&P1 zI+M0CCZ`rvL%ve6KmgngY+;wv86l`~!KzwunI!YIi{!6jRIo~)pKor&4@XwfNt&cG z3CEDma3o39WF6#7>r)b+mq2OzB)~9mM^I6hOIQm%^5EOo^lRoR94hBf)IyTm6W;ICbqsS+#!BXsh5Vl;8FGIi3Atel#B>|^{&az3UYq2kJ@pe>O z9ATAq;4S`6wW$R->I2tY^OY*?aBor~0fNHjAsqFh>9K!8C~a6-`T# zqYSrf0YsT_*Zs8)j`7hx)#YK}w@Wx1kP6+O$0u9FA8%Jf(s*}k;4quNq47D;(t_)( zIHgZQpDFk|>pBd2xrLBqH9Y}&jt!1~*4D3EMKI@W+I_aP;ygr~hLsgeIgRm!5Ap89 z@{$C3NrsaSt{kbmSf-T22CRzA55g;!)**em&Q4~~XfD;SRAzRNrDa3&&PuBmM}7Sw z`ef1_c_&WxHAxnUsEOA0IcT^s73&OCY_j{tDIQhr*V~lVUtJ%)hB8lT?y(l9&yr(b z`)Td#Ue2IUt<%9)G%IGcDv+5XY$_;iZgYZQXAV^Y-Djg-x4Ij1z# z20|SdGOtc=E+FWq*~OHmGG~)SND;ylCB4>EEFHBCEht4>%Fn&rkhv9p1|N6>#g1sq z|Cc#G_WwKV%|BB~|4bnLvv(=SS3l#wvVs45g84uE8}`5boBvh#hW)=2zG3(eU2*^Z z=zpXo|0mliSn27zXOMK7`&-vS=(p% zHRsy3UDWhvc4bS)yF(X`im{TR)K^|Xm28-nKB-k}Ab4O$NrZJIE#uSP!QTEOc;IzE zKu1^hU~B-4w8?feqjm(Nyg7}$oU)Wu^d_uy1TeA6ruX~tVZ!)6c)<9_J9--4=lwYr zA_|*f@0B+qk^xo9T#}lp!~6Mp`t9Q;YZIGgooCA1QV2(v`^tvbvbL?n(!6t9%g6q~ z?cVNlaPUx9hWFF?^}$AsJq$zHetmD6+b^HOt2<0$iZW2_2cNDT1#o`m{5yYSihp1FKz`ES(CD5>~Sicblkh2w$U4 z!K!EMOFo?_yR*bIlI!=7kL@%pp6Y|=irN(?FR?t>B@dlQe#4$vDctoH70DCC(BB-y z{px1=D-+w>uts@IhwStAlJrpq?MI1ZSwxN)@*+xhP1yk#?eP8XbD#6;70r60hzm|Y zY06oNKNSMiU-(UW-ytk(D{w`vUzw@`j$zWy;pd;0u1sm$i>co)fS+<2vQcP#L<~zH z9vt?dfW9C#o$23a_+s{XhANRf0-_>DLi7yJcJKC@Dk=~G+~2>^tWCtdlp~dOh$79m zIZQRrUgM2CZqBCMm9F$Fv#gkZk&+nQcW5W)Y+q$fGS%1qZFM~ea(4(i%NyK0!DL4( zJUrLSj3|e_qaKGBi-SmX z4pya7r53^I6$~Tg(v1(o@Yb#SltHNBb{dZIRr)xxl;dx-UYr2pt}yBm7-nYUw?D z_5MKa#-|wpJMbaRGWc1B9@2!?NfFlIU+)Iah561=IS(Y31nNsN@TyUnHXMXMqin>F zQ!mnI@7AQ#rDy_aO~E0-c%}Bv96P#~jo6D%D#NH-W|YuO?{s_6dZ6m4<(yD^tKrPT9@w0(B{yIx9pH^F`YS%bJu1fk37RA8U@480}M0_ zaks}!qaK^W{*h?Oq|GO3s-mDaAK{S-JKK9p5^umpw2p0JE)>h}x7I{pT_4X7C|otC zTbkE?QVpb{cOYm;c}49|oSVJKLU9BhOVbMl*IG)fp)!NYL6sT|e3i|{;<$J`gPtI( zi^~WcmMyxhMGS?Nm9?n}9NzfmN~Ty(6uZg#Xi=Klt(KM+aDTFTtN|L5xP8G%@6Dr9 zbOa>#o_uww`@{@4a<#B;q}M@MC*Ub~khiib*^P?HNAhQ(aQT5nqBDeF=R=uLA#^^RPLq?SKpEJ;tY%@% z3VWbvT{H`2g0`y+jU{10M3M5JN2LbnI-T5tGG#OD1%Oh*ol044AJ{RU-1gz}%-Gt? zRWW8`F2e6L2vx>!g9dZoYqEL0x;B5yCC5UMiKmy5B>F)$b0a+n;?K7r_(UG^y4DwK zRjCBpE{rjSb_A>>{Xe|Db8v0nyXL#IW82n_v0~fFj&0kvZQI$gZQHhOCp%6$-`}ls z&*|!O`}V2o?tdoMsyS-BV~#ayuJ@Vm^Qn?`xy8@)u*0W*!Z)1kqd5ny3jBJh?a@*r z2*`XjKjlSWb!Uf$PddVpw=9+L$MTczS&I*z0AuchaL<}Sa+8$nGvVFMMAI?xL~kOr zf$sJVQWg(-V31FNKgJTp27NstE&d?F)T4di<&9yU0-}uEk)0?hXJIW_j3yX9Q@Gov)4S@>dlG z(SzG3^j2OSW@m{7@Gtp^C7`4gVu=9vsqP5h6zKK*l$<=ZxnQQ9CYXT^?2?%jpb-JG z(Q5A&Y0&`*`+2ms%}P5G6x7h`(SH6DvpB`OFu!EB!Ec8%p>cgcs69;tSOS{TY z67JZ-XHyim9c!{N0a|&12O+0=&tAZ*?=MpF550yKK;)p}j(jE%}yAHZ1Tb|NJvp~IyrN1D1?Na#>X zD)J5IZ}Z*r=2=gS2=iYO%IbfpXsC-ZsH+%T^LqH$xQ_j&{bXG#9{v1V!`m$R8OKZaughAEvO%LB8A&c+1%{EYQ! zE({yZA_k?06u+%k8t)#}<*9)Pe>AP;R!GZ+abHnd2bTl|*0zeF#pWTT69>HV6_E^w zHO4}la_L-hCFvr0HllASXklLGk47-j{=U|tz;SnDbG(mAk=bE|4Cj!Kja@N(Oza0L}%1g-R+>zaD7S7j|LaBN*w3YE(y)duKy!F z(Wdm*Op~I+xa|YKvZR;qMNSoA&$xCJyl zkpSKj@jOJj6A8Rtl$tij838-j8C{#YPJarqI3q1(%wL60+RT+Xt%|~ zlOJ8aFdh&vRBH@r87V3w@-of0&0o)61>}(St-0O)wH#`#@!52(W#+43NH+MAiNMsX zyCge&EXszXDu%W3j6jkZPGvn3zY_j%Qvy^%5I`Fq@z+FmAu`dVTk-swD;tZxelyqb zYZBCw@pbS;kwF5j{190ge|IIcAP#JcZznZP)1$4)N8gZQ*TG6+bM1kn7DOW0*eO@S zB?{vML+Z>`oj&iU1S5*ANp|G^`drSF1-PIpj zW8(x9b{B7$^XKZ)upwj0NpOkTZoV@j--UZILT=^`R6D%DhjTc>4&xRHr!%5hI}(_) zL4yKi-?b~vOr5}P$r8!#Ey?ty?QQ2TFL!X^<*H%FQ5jsEco^$O_P_|+xp%=$ z$Oi8}c;cq!L5O~d1gS6zMiT1>UmhWUxNY&wKntR;#&4&n{UH|G4^JLMp}crVyqZwO zkQ4>`=V}3)i57(6)6y2ez5oufY%S|PTJ%U{SOhn$4_y0GR=~YWj!74sLQ0n-(2|7_>u4v7fvVg9*3tsz-+=5(oA%cBr981K!G&^J996n= z?`sB4xpu&Odr<`Qe(}wFK@Q|b{JT&7bVSHzp7@&w`$yK+IK%=za^WRga*AA$M_<=> z^`dqW%<%UHA_QxCe1J`QseL19c(Yo7cYM)4a5iW$sdBw>L*~@P1l)#MuvpS?$iryX{Q(-h5}--w^(#!|LdM zz9g)-OCtpL27#gl?Jlfd(qBuCWu7jM5w6b_PTM!z*?UTotQBm)6&`IjP&;%fB}`?t zaof8*3Dp0xY_^GAsEuh$zV@Y2(p>ABemVvN=h4=zk~NS%ZW}o~Z^SmVe~K}De*v?C zBA&+VP@d5}U4ehi^W19)cMOq9O>Nadx3DahIZ??eV?iE1%hn1b`V?KIplU}L{e2Qt z;IO+tOBH(G9jTD^{rXSirf)KG-J>V&0n}3VcvzDLoTJH*=KC@%_vL#pC zQvE|sUOl9XvL9!U7=7z(g|>$gllUFLZ`Ny}?BRQz_ zNsc+6?+%&%bMHGezXQ>awsVa6%5K^={b*a)d5G+Ij}}|E;`PoMRMF8rU&(_6*EQ>)rY%#Jzt)ynzrgya2Z_y}pWThbQCu zQ3R}@cZcX~`Lpm8S&jK~g$F4pq?0ACMQ_)rJP=t*q;K6uO3*hfNVRHLc2%Msok0`iZ1GLDFm z&FdP>44y+#Qcd26GXJEB64BLa za~qb^NcXf24e999! zVX;p;=|37DQL4+N(QhKHIXUDTBSrWZ+jFl_u>?$$943oSVm$)Q5G#i$1qSpk9rl@w zQStTsK1H)f&SW+(QhJ^L`eSDcIkwT8(pIH33YAEDgA=sbLtjkp{`x{GYec^ zQ?vm1$_4U$OQawa#CO#7l6()649SZ;w=!Eyh3gO!-B2VfWN=bIU({<(#3An4Y)yKj zg&xv9*3myG_KlWtwkkjb$@)&Q4Q1g`Z34y4(O-O?0w1|iTCLcT<(?LEfaT0uK6Q;I z(}HlKus<;UuBtPsZx@WzXoEv3IieI=_N=;*=p@Se<)oCzDn%)s=jd036gZpI16%W* zzPO6*jw+TM$mW>^K=?~DU*&m$paaAP@q3|nLZt7WR5f2Ofo0$ ze`)Nj{I>B;+<-NjYau9AK?t*vXS?!0j|*u(WRPgpSR(5s zIx}am6#If^_qk8 zPHY5jt8|%-2Jk=HS^ZCx0(J(5e=hqEN&&aU zKT5$icU{6HLt-=!+xc1bBy#pyB&FqoKdneMwwXQCHe>nB^ zaCo=x!MMILGo`b=_3`-rxP85q^JV*T`+9V@ z`}(&C*XR+8@KX24q?&ifcT$zH(l&NY!_zBD5HdX^_2c{-e|l6un0Bds_7%bV`4#iN zyn6m^)UO|rXj4K5zM!(C^n?4gyERrFbN9S*B@L@*T_u;6$&#8a_P#ED?na!;S(;`NIvH9fyP*Hx;LQ&sLERIN|H~+Juz` znu3J%x5zB9$eP5IkkWW3_0F@a)7y>h8|m~IB{%pXWgfBv8xk*re~}3RxQBx>CqXC; zjb1^Ns$?FG6%bkS90V|xp0Y^Qgfg`X6fa$n`npP*-r|c(3M;Onvb6FVTn&c-eQ6FjvVt3T&%r!? zxy+){qe(Q&l^VT3t$ZoN+_Q|fb`5)%`ju_!Le=v zz*H+|B;qXSOH%oK<%j%csR$QYioK(Nz006l^Ff{UB3JVBniJ0# zkseEdimg}~^DN@B#O0GBDU?V9>{hTd)5PUTAEHCTh?$6dDGxrlw0? zv4*)&8yeNsTNAtom%Jkh8u5tbL@WM1+C4+nGbLNO!l|1{^2sypB73Q+Y(8p+w<<87 z0e%AL7bL10^QuQjL*s7~qWZF-CVW#Fw1kO|`g<5mj*m=0@jE04eWUnC? z7b0mM@JPQA&n1!r4o%~COVAa^1lXNDg|a`hcDI0C0Y);Ehp}6^v3BF}cM> z1j=VgL#$T)8Kt*A0r)Ng5J6uy87?&1SrqQ-A;W=VQ)#2ZzS`h}RL*mKSV4aC7Df~^wfqxjSu3SR_0wb(qH&S*bqum~c$i8)4R|A^f+q>f z0ag3z7~V8&twd)MLy`7*m8#*erhiJo5-HXFku@5FE7$gz1%Kh2bd?gh5g|gV2pmV& z>LN8|I(3z`w?tIToR`idPeu2Hr>ucGhx3`lEmidHJOD;q{%}2VjL>E#YzvxGdwNAz`$OGS&ez!`JzMJo_{DI6+P? z`?|B>`Q|}dpyx)IUH&+5f%biQoNV5n`3q+JBRB|%+>?<2(p{fKWE30vnjB9j%dZ>a zE{^u3vB-tVBV{+3rJTTZ)D{9=*TAU7cP@ELe2;~-4;e(GsvoqrzWs&-iM9iK5oRg! zEWp-=Ha8G{4j0#7Wb8QWrxuZLVkOnJq;hnl1d4j;55NdrN<_W8cS6Vod#PkPk2V9t z2JmCk%uL#Vb*^@&<>>{>uL_~cwn3|GAv@d=iub8T$%Y0!CpilinJTG{_sC}!A~RW~ zmdQxUdS9t9P(xt|fk*Js>O9OOF^|~47zdk&0@L_aJcs;@`6C6dOYVHJv{PH@j3|(& zEtEq19P@=AnWUN06C#vOZse7Fk7gGLp`8RDQpi9$~c)Z}JrjG(Q67M4b(gD+j2 zC>w*`ehESRDr&I|gEb&uw@K@?AF;!*y&B{Eilb8Ra ztDZc5lwdVKbAzHM!#0li>w3i3il!%MA94J6TUw|>S&0TL)NU0gW4bjfJPbCh3hx&q z3ZZ%_0(OVVXo5_4R%l{^;GbWyWI4{9sL1Q7FWh&sCk+r#@9raMAG!m4 z9=jimfV$lNa`TiOSNB=;PwfH@%n)7M)?%W!PRXJf#OrTr6XQ@60z2^c0bv0Ilw#>9&*gisYFCqx2NF(RHBB-Jnum`u;k$)q)ve3V$jkKtikz_tWCnD7$6?9#o&53 zdAFFQ6oZ`M*7EXsP|}zXC|YSyE%QV~Q*LiHcUiQDj<4)8wrKR$T#G^=p(1#{Y=zQV*RmT4o}Y- zHmfO1sjR%5RHg7oK>Y1K#e$*9dW+66+d5lZ)Z)-T@%nl-T3pv^l1I00fdfPq#xnP!GKRxBU88bAY@SP_cE#u5#zYnHOW&#`{AnsQEUs^=xb4hv4QPa zsD`^)NaUCjF1tB)ZhfkhVm_l)#qTr4BEmd(zIcc@avz|`u zH^VHOdnIm&C}7dJH z)sS7j@jGp$N-)ebp**oHaC`GD*f}P=7`Ggbm*9}imT)UrJuvD0HN>Am9P9Grs2B(x zT5vg5@||J*OH4G|H3umSeAy$mA&2Kj#e_-bb z4O%&z76QN@dS+L8{mZxUgerX#g$qTmaAzl`?}|IN!E3qs&%~Txo*>-p{`%3NNyda3paz+;E#pc2oSXoCyjc*B!_j<%0ozBz+2 z$%LelZ?-Y#fLNMz6rMW~2f@{*U?Qr=yb@hJ7^A^`iq9stdk2$y1w$B62{j`8zdLvr z@~|1|z3u@v{*lb@9plp_Ler93DvjZy)^(sNjs}v+&Kla`(rw_g;uD7-4@Me}Ux_uD zikr-0yLQKNJ|Ak}Q4(vEc&?p6Sl@&`h^RGcEWTmcy~C7OCRJ_82dU&*IPVbE?wOaV zlIP$OE1z_Lhb8r1{7ve4!9)o|EEw&JG`4+9@-G&d;N{vlEbf&+kYPrhWzsE3vL#2F zg(Ff1Zg>h>{n1S?>>p63B%_8QVr$9}WynPgRS&YC0Xf&}3hAQ{rk=4YmSCK#Dk-oW zKL?I@a50LHL3hU6)4-qmR}X5a2%GL`Tkz2qL`jJVJ_!~dLvOSsCRZ6g)gnY8D&T+~Si=$%k4J%cf%KK&`bJ-7rLYD|;j z_|XzEhGW}E(g@}3C=?n}rBb6*@qO(h?Jg-wsg~67=`dw*kIkbU9z^$Tk1>_L9qmsq zn=?Xe+(v?3O6;_bbaA4@bODJNBc|13OM@R0l6B=9DaG&6A5RZ<;7@P-{N zuF&TScqD{CsG9}Npw+^{?M-A5s(z*SF+w^JA=Oi>r$HUd>sURX#Regw~Trz#e7yo^+nOr~grrwD>@_kE|rY?@!5c) z?k_soqe`5q=?{Rv?L8*(%TY|gfS?!asW;!55#S0*!*~Sk#ZT`WGPGEk=gt%An z4Kk3xEiV9w64QIoX~}SqG-gatGaIxaF+Jq{)!_5j3D44Y040zu_7sD{o*{Z9bPl$~ zQxC&G{g@?B@a#%2?}+246SBb`yr6h4ENIP6+ z)dIB2c)=n6yn}c@mi4|**6s?P8=i({4Usq!HA*TGf>eVK9vc__hR-p8S|InsnDm-m z`b+}T?W&WW71*UJ?YNWqVF`4k&N2MZq&zBEK=k&YJ--`!E6~}Lqo=)7Rg@8p8(u?| z3uU#5yN6MvSHt_fZ}x=*Fwm<= z*(X&$2vFshof93Xp+=}DAQdA{R5#<}bp;j)Z+oYJPjh4;nH7MMGgh|!LeZT}Tu8du z@oIgq_B3CvhcTl7&o!ulNl!>c+BCq;Gdr$7&!~p~<9UMcdOZEY|LhxEg_7Ph%l9%Q z?r$U{r1?67Wr);40Vxxvyn(j`@+J&5mCa1pwGk2lm$4lyj{gdG)|e+CU}R3ndX`I- zdd8ksUM(?@4vUF7dx2qo3lpaOiE#ANT>W&M)6Y+Xbg6fo39fO(ag8P38{|MEdp-~v zod^rGugssqpso?UEJIoeE8{kTx=o706> zAz=~Syd^+K!L5U$MD0pf;h?@9Nq#XA7`*M2d=c1N3UJt*Q8YZ|K9AC-(EFD6H2k>% zEIJ5Dn#3Sm%c~R|?zs#fZ#CoejR2Y9s@p3p;VFyCco5yS%}#fW(v_Ce^&FpNIaT+q z%uIHSD#6KomVTMNg+I%xU0B$hl$(Y%r&}28Yv5zttmel|aC$g&jZ+w#ZSI|oIZBIZ zm#o?-wGzYn&BPSDYDr>gv9Yz*gVkJvL(U1QCqbw%y_9S3$jAu$IpqTTb6sryN@* z*}V_G@%Y!Gpy{Bf-C2;5qx;hI`nRJ1Bk$KCH@AORIrIIQRm(3-%a&+9ZMi%?p}~^* z^P?LD_&2D9rWE-F#t_KZk=wFTLnU)^i*bdI84~ak&th(L>T`+654b?76tzou4ES9f z=IZNf&i(l&K6&;cs@gGdYneNAxNIAcUbt*)n1wa8ppt_(?#xgL>{(hN((RjFA;mRa z*+7Z?^z9Juj4##a7n|sJ|m{Z-14!&6pxkpgbc-lJggLq(%?{K+YWWzZMva0XE*I%yc1RKb6KZ5M*wXE}fI(91&p zyYB2VwLhb(Xd~kngjlriDgE!phWlv8O`(F!Q%Mgfs7hh&V3DnRF3_kYU|K=qYo}~~ zk@JtgvwX_D<;OlcD=*#D6YuwjxLHcxEu`Zt5BhKYUx7zfUqU`UpJRKOE=)4LX$`SA zL4U?M)5xxj=GIGf%ESs{UFh*^Q^sdTTyQ)Gv}pwb%!J=UJ2yo4(bxW*WTwqH)|BMr z8mjkYH#ppiD@l|Tr}QOCBHQzk!$lRR4Cq?y?ekz=%GysQ^GyN zJJ;7S@IMy(UGaIBqm4ZrIgz`jHwu3xM?-GKM0>U5L~O)t!b#qD9|o})|K}>pf5aU8 zpO!2By}se!O|$-8xcEOQSNw}V@xKWZ0r;2h@Nb-mZ_(-B^`8HUT*1cvKX4)}zBv)j zAOCP7YINp0eI+_P8D(KgbRu9GdZd;DKyKhzu+;VeL&;pp9jl;s`wn#FWl9y`bf;Ey6RR~)kaiO z_b*q65joypcfteOj)ck|^rMop=9TRawE;AAs;xB3m6)d=Hjf-u~vc%|SImMNwa-(Sw&OVx> zh{YyWjw4u=47lEVHrAkZRzspF=>nXt50ieg38#Kf6U%zcJsD|3thZu53@9Y>O_1T6 z4O4@9@y9lHfPkVW?{Cw^wjNnQx!j+!&2s(x{&RApw=!H!ScZz|NuT+% z8Mf@%v1jW%-2NtHTkaE2&M{4kBHZyNU;%x>Rp&s(5EXQw^?47PAmn8a@owM?=`+_g zfS0X>gY%XDd}Xjn8X6Tgk3s20GIj(?A71=<6gN@^WcTl;^3DEcPQ1Sj?bo zadjw5Iz=&c2m3ez1tErhfr8R5ulm+8=*^m)xdEdO7+NB0vKJMfEyG`(XfgQl_Y!dtE~pw3Gh1$*7ys0HZ9O)$^>dc8iLRoaH9!v3$vb!s(Qgv zIpxEmORLY0Ne;$dm>$7$f<>T8etiFwH_ze2vNc*q&4gUXm$j5~?dM4HMU-2#)twl<1$Z(jolfqO%WupQ>4KUV^~4y z5+p6t6WYp>Svdnafbb&|_Ko&mt<4FAS(pW~=T54SLD=@0Zn|9eN`EULy|JHx*Slxv z%bY>iIu_8m(b|28&Ue1o<(-pb+smdP!eZHRyhGgK`jR*`2$gbCB3_TMX&-W?Sr2-% znJ{d)k=ns&wBmUs0r%N7pyK8L$`b3P7Fv!ZN-{DahdnN4TxR zsB7u4=5fEq!Aw`1(9In}*qPlix^g1)#%wOgFjmX#SPgD=E5fH9_x#W2^&Rgs-U3^* zWsIKgH$yIiRz3BD8cex;EJ8Y>kHR&SwhPrOf^SO4(@_US;82V>0MhZ5Pse}UTrJmT zxTAPQWXzZcix*z#-J*dcsOc;6a&oHs_H9^NmR_zPy=7N}jxo%SKRqu2&Rss4ryuTb z1@P2OJ={5T$V2>kxxU;(vGZ%kbGz75EbwcgohT>1BiH1`GsC$#eK3Nq76sjBAJOz4 zpOSDpk{;HB^}*_si=uz?X^qI#z;(#Mb;jOEcHEl z;sH)ukf?t_nU^%U&){hxcAe6c=G{ybSAq!E2^@*yi!-J*=rddr>$&W*M%oMeq#uCx zT97YJ?!Ajz^cKN#nBI4}Tib_=eO@+MV$lbT@;VOP6Q1Y`JMCsTRLFum$SAB=WW%|v zpKo`Ii(~M`QFOeAAN2t0)t3Q(NT_hR$^#=xUzRaI3Z@?$VpzL3>@I1s`q!K*EkP;1 zO01lFzUZM#K)vg7PU&p$u`HAfCxJ8cY)`r?lUa)D)O6qX^kTH zte}Fo&5Up8fAy#3zpA!`JUL&;VO3Frx{^o-x4pLVj7ELtv@wKE^?~5i>_Q^ z95!{Vd>;G;(OQo0jrtbE+D*YKePj1cMJOl2aKV_Q0`(UJ(tf~}B|}pFba#oQ=n#_a zTvnS|Y&+3U6KsxcXygyu5tu?HjMNK97*c3Si}o#jPhGqlm9L)tdmih|sWHX;P77Mq zOkv26xJi#HOW_Gki5fIhjC)bx=i6%!*Ix8MA&2}6_K-?Ed9&oZC>RN8M3wc=^F2$% zq_vuug$A#hE4rFk(;h`uZY-Cd;^j6u*A%org`ETpYmoZV@cq?14^AmaT84DH=K2Do zt=%Mc^CP^I?os6{Dk#Qa)*24aVeEcx0^deptgq&tl8E(Le0GMFnM~o{5 ziIy9}2*o&_wpCFi^y}w`du#X;Uei2;VYgr#ssZRXeyKqeRr0ZdPt|NMV55g_GLMl1 zEvt&;9EoIAXoqyzHYqw$5x7{O@)EwlCX5{RQI_SW_^gttrbXdld{`TbG28q&7Rouv z)-MUFMe~M2kb)*uRL1j5BQ!HT{gZX^ge2Sor}!A&PQJAU`0#htvcpSLom<90u)}Lzk*JeLT*svrKWV9>MQE2dKK* zy+6#UJ{S7|vrLAY2Zhi!Sb51JC@cFMJiyv>J2$2h)K!(>do(&GVRl$9q_d8=H}dPF z1t}q+EF%bjXZ>%i_iPi!H&XdhdIoVCB2S zUYBU@df}h$HT?QsB%HcEGS5HF|80?|PE@L9W-&ggR8*n8*!p<4c743|*;%zq`!mCo zn^HlhN>nQqwiXXnSZUd;XJAhQcH6Nx-DR~DU6|j1*SyFG+xTM!cU~5+niP@cCd9*O zx0*m#HE0&ApB{z_h&K6L#HxGM)8!pZA>g_mIDr(nfJVaR4j5S^Hw+nvhN)t1cPO$k zcP`E_MX&@?-;TzV!yq?2mQk%pM__1TH&V({b0LeGM^M`e^)m)dxp|w{UnloK9ns*I zegQ%`>5$BTrQT6MHI!dL4KJpKwmA-Q^qym-!EM1d7qR)nr9($HY1NWqprt548Y`mR z5ymH}5?XB?EAB^194Kh{a3b6(B*VN$5g&G6MYT#IVrQH0%;5n#gX5sMJ3pknr;wVw zXNY_uHLu))t!D3_)=@f89kq(BUk}p6I9*H*9dE3kPp-b!22$%E9Uo7h?nli8DL0if zzKLa_qQpv}Ug0Yz6r7{PEUDjgnq##4CV|Bukz4E!$b1@ao6x|^+|enUA&r;#T2H=; zDW^r31nuyGY!|Ph?N^M{jA{!D8I89zYw77`2EQYkijwT*A}~yc22OS;=p|A-DRt7b zO&O;Bhg=e8V$oO43ni&c58WtEI(NG1QjyeV?NBvBjKFE`+#Vy0wY2NUO8(X}ed`i>7+QTv(g(|l%`*avqo7bYr3)Yx<|kK{n)4TA&hdzXJEV3;-39JM0Xi*~Lnj|m%M6BiP5JUxjznFeF)_^B9 z*@>)GJ^^kNkJ)bb9Zk(H&JcN`DM{+87o#dK9H6WeVUeNt{pIra{d_W4?y6^xkK!6? zh7VDvLEyj{TNWy;l0vIzg`OX&8sXXDJ(R-FKc#UGckF?uvp6=z@t=sKmB#T?O#{lV zJp$!rh7)AI17qb0fO3D|)i60Ffum_&LX2P@WWsG)+G9(}3I++y(JJY&?|oi@WJxST zx!p-GWnss}uC!#RKL40adCDDw5~o=h)T)xawy`!@QTP3?Az4E;kWq8Z*Wn*%JUu8$EgpUVkC_5&fKRdaX|gli&964isrS_sf>C~gQ};wn_OlrXweJMG|?68K?WF~9-pyhq=VpGM<%;8va0 zU&3d|eYitEl=`ybSKJN$E$z}5!5MsQ4y*mNs_k{9;}@v(ekf?rd)B*F4aW$Bs{0b02h=r0-L z$Hml+Cv4?Qhz4qD1Gb8CJ9Xjf7sc9``JvQDyS2=nHmu@8dH3k~hE^7UbiuNh8~tnD ztQOks6Va%$NDmUS@nIRscyXq*5IFZxhfr8o=i51&p(kckurAh~psuiZfd--#T!voO z6n3BKs7jbwnnF4T++k==l7{apm~qm&TXhuam@*L_Ti~V}>1^2?+z!1?m?cW16tosP z;pSXPndLP@v4enQgbOq3UM-7WO$Taf1vVb_H3sAL8uxh!fFO#0;rn>qEnLTkkEgf8 z>n&~SLevKDiKu)WaNf~vLMM}1f|VBXGiQT5?-WH9QM*>Os)zdT2x_V&0T=Ql0k7k%5SC^4u`jbY|zrg>1yJnZ>xrAhuYa2+-VCTSO|_XkQ7u0e`pa= zz!}pRd&j^|*kt^dI>+;?D(oL@vzg`j^ONiPOQoS>^>K_n<Dda`@n% z9hSk77y7g+mgn;K1Zj62o7-t0N{xA9ZNcK@{*0u(5MZD!^DQSqeJR4aO;$|*(@W5d zP~9-67@($ZR@Q5%b-I&JN&iCNY%51BI}!6lwo9)w%Yp^7GR_@m*q*6+wNJBJt6hrf z#nQ#&n>?I=z0oVLz4^gS;wHPaQ!$^T3=(99o zF}g{?_Lhi`wp)wEs~QI-rMwSL#V|C%iIk1Z%oY#$RsUC&EIc|;+L|oYtMF8_s>|bP ztb}6**iS$*ah}v{@(27&<5YVOeOh`9IZ9x~WJ1on^lIF)i7vI$ zW8*N61C~zZEDB9SrE3HPKs)p)0kbaKsJlsZ;TH6gYJ-r*Ti~~jCB5KLfP0?MV?o%_ z35@%=E@V>5)8eD-ZNel_F7nt0L3LuiYzQMWgFu!8$s1R+g&HVIdNq9Z3V$f6B^kKS z;-3jIO5k6NKe^v>^uA)?WK66CnaRe+&@>mtprWT+REyk)F8*rnO^t8|X!iu#->!oX z-Q>3m39WZnlom4kJtg?n^zAOzxZD^_d@R{?eROe-W(gm+N+IpJJsBd_ZgVP)8^oXV zdM0VQ;wAjCC?&IhQE+V0n8~ndmbd?XGpPIUUdzIX7Tp)AjCu0B<&~U+u584wI@{aF zp?B*=!}Mc=(S&u$2o7UPfVbz3H3B(x-oXE+hNyRw*J_Qa~aX)DVzc4-K*iLov` z`l4ZQ$gyok8X+=&Vw42)LZggxA`}?ElmURT@BLfWNZ-`EV z!cXD-QLq0ec?dPEEfs;}{yNmi-i>ph8>tLu2vCH~iZJ-%3np?0%Nv4Mgy<;4lE7kJ z6ggs@EaOmopJilm1XO3Z2|8><;m?F(47hP4kTtD-{ekQcd4QZaCI}+=dIctboQOHh z#CVrt)-ejK{P#+}GzulfxuEAD2CuSnTT+sm-AEjxIOvC>~h~J=ham(C?_E zTerq4r2=YvD!za<;om2N&mYNGZE6tAVwA`;_<(y!?%}AhSmy_pp#n%pVnLYE`Y9Y1 zPH7CRq>EPXXHNg3Vcm~&|2ZBqqB~7=zlf+Q%BmVUhstKWr>7(42`u7(l!D>x? zPCnF#e*jI^RWyBd;!0RPLeLB4x-bU?SQhFBT^KtcVA05yi?LV-95uEO(FD}c42>-~ z6KC>8rcJxyE6ysyWZg_0wJZE8CHC@U9b|k)qSW~?v{0zsen385i(MZudWCuC*1&sm2TrBBB2E-?Q{vHWtFcIARw)Z zqWw!NNcCv>-C%SjPVGW<6hcD3!&gUR#waMs6D$7d#JEoAS*~8P ztTm3BfxdJsc*s}E*K@yheS2Me)9LnZY+5%9EF-)vH|O(i~59?B2=Wa;JEj zylh$iL0M;2*jO#2T%t$ZLqjM?G+yV0i%tMZH7p_S8I?wg7_FjquLPQ$eQS%Cd(&qsqAqfpzX?_$j(uVw zrsX;2yd6H`@N)AtX1n^=Oy(}gkcX3=#;mG>KaIkQs6LIQtdd^|xzM^YkvY_CtAr4v zettN8T!b_|@Q5Vsf%IvXL8YM$TeqRJi|&%u$MF(I*XGrS35|eXMwIpWk^=prtp=by zc`rp5ZgLCa_Ctb}x9Z^z`Tgtf)$4tD^Z5D&eY9czOh=skgiukWp@8-w#()}WNJVON z!ZE@zN`!1E^)m#3lrcn7;zTigj8ME6XP^WpK5>FST41DFrrg;rfODS*S{jO?uj%*;$< zX13#dmEG(nt5p(h@~72Osiaf&qpPYqTF0avm{R*Fey=T^}QMtzCm- zVqCSLO+UgNeZp9bVW}Ly;7U(w=~$!7g{1b|(7HkhiQD8baPo16s1KZ(qrwWBaqgoN zAsm-+LhG;tz`LMP`%uv#;YQZ2geG+gYkkky3vN&mo=QR_yOgY-2X!mI5^QmTbJ|>P zHdyk!LI$5669tp73W(%dyK(OrG5zSK@gRsC6i%~_t!qxZv@h$FzLt1ZnH6QHB0?|K zuN&fnXiZdK@yOKJeOWx^;72yIs6EI*ggMD2ljhC5fQx+FF5^(UGeJ}f{pHu4lU-FlD`!P1v7Cq^vxR)V8gsLg~mW~ftWz=7uYUC8%%lb3rU+ts>Z(A;WYL6M9w|+zboJ#Fv=syW(6H}ItDaUJOo(@KAYFfwZoQv_#ScT((utQY!f&lj;c4gbZ+P zygd^%4pkiOF3E1Xo=mf}S9>1Kg;@w|yg_uz+w|G|_zO?aZ7ROa$*1$ezL208RM$;p zp+C&lSMuiMtVx12k zgrGcpL#N(Q33T`aco9>2bb~v%9ShH$HUkQjyR53HXwceb%^e4gjk(R7^t2GP@vu@v zF>;qf(@bsR0;>VM+$Xe~OEZeH85~=+(8M3Ry#PwfP->Y9wjcpheJeQb z@}`ib^;DBCCk-P6a*JfiH1Z=PHC=!Hq-cU!U28<{a*nVas~8Ja6C`fhy09$HsuM8i zBtU6?X-}cKyAnI_33>faqji(%Q*6DCf64?bV9-`3KngxW0b?&&A|5^M zlqLhZ|NFeKKZ0zr1iuS-kUI{IBC7X+Dk-;om1619^8RmhxN7@+(sFe?3Uem-~WC}@AU!dGMN)zEzQGoPT@t4l% z63$IBol|EI6o6*FvxulFKb=&AO6ODs*i9N0i%f(L2F8NtXxMa4;L^0sL0I(|HH9>j zPZe|2ct%dqw$Qn9e2)f|i9M|dJMDW1O476?0iL%5mI=CE)A6&kG8@}gjr##`GhtL9 zGC_h{a&0v>zS@I{}M(L*PPy|N+_!}FBe~)f@?1WNo4!gUscvaR%E@Rj-Cc!UN_1E znUQT;*0eQcvw6}Ku&GDoEmqTPW`7spwKp%qb=&E_E=!-aM6Y__5g6F0TstKEOzUnG z(QN(wfMnmt(rQ;vC!M-Mw!n(j_;g9pzY-Oo=f`W2Zp3l{Ry z7lR+f;PYK%yJ|5#&2qf)**S;t!OE5W`?t*dRF(|-m5D}ZD=_m*=TvRKASbJkqUp-~ z4gjuGMn7qpz8y1gi`rFq=DR68{la-(7h zHHaaDz9XtV&5@F7G98A#A**c}%)}fn4trW56MW|PT*sh78ZrQN;@NlH;XgUCb8&gH zda#3lLlX% zle|Tb8q1JcZqa^%I#i=>$y^mWJWecI7( zJj)|^8t~?HN8Fv#_QkO z=+-SYzq^~a%-a6;a0yr^GZ#XflV=qm{J`4rWsBSbOQprVyK{GmD0!BEqtcHoL1g@g*` zN2=Vq7Pg9;)%w7PCA1J<*XSS$sD+Bd~k9N z^RQ_=JR`uX8trN!Jy=5YBS|PP~3fR5i z*90B$BL>M!|4!wB?(c&FJ7(@I1C_7B!s!Q$-N^`qeHCP)*A4b#PjH36Yw;ZCb(YTk z&nl;%HC~ImW|7pFZVrQbFbwHy*heL2x$Xo%H7MAL7gmN^pd;hRjmDYfLpccgdv||4 zI?`vJH;61K$^Ue+>69O@I~zaKtjU}E-Cv3WpXEnb30^S-=KVFKCRPcE4a&hH8AT|~ zCczIs>`F8gsy5yn>O)|wt&6zI#|!@vU2kpu;$jsJfn+%!U`@M`PfRaBu{`d;Ca-+x zA|T&qd>4)^Z~!$GjfermGXQ+PzZp!)T99{9*Tn z&HoL;X)LT5!sVGz(}wAjc3wSghA#5&{M~3E6hfuPrhNpDs^vULFNi@5)@;DC`Epk+ z@Z&GAm`$I)T+EUBYQ>d-hog}R&xlL+?9CT|X`q^{O~n<(nBHx{_E+yb5!{U5Zs2(x zNWS%YL5Z)|rL4!h%X=s@w$Gqq!JCX=^lB;-21~kA$nmNZA9W9mO?!0@p)~3nh;0?A z#N?el|NEU02`kM!SymSkVSqcqDnI%1J{f`7>1|I0gU5Mx7f{+jmig51mUQkmBC@w* zvIBdXVtlNmV};*Q;uooIE|d9@Kf(RWadEurH-;Y@UXvGdC1Czs}Y!fT|ms)-@Ma(Qj@Kpq4C>^dJ#{? zp1^%XKkCa;WP^0SyiC}ZfP?fz()%L=-Gjp|PPMQ;>gB-q_C*)RS@cz2r zoWahRqL&>Yf9K}7b{IT@hk?|S+^791b}MP6zc1MzY(7SXB>pvjyLNG}IJc=)P)RXh zG~&N#+EYb&#V%;#^WlNu+QIS+eqyg)KxYS3Lhw*)Zg`?BOIhp1e5x#a%qU9UIrHf; z1S=oQ#HSd$LtWG4#Ii57k7=Z84y-c$f!221N?j<;ic zP}>md2>*4P%$QgJrqm7#El@_#Z2FQW}}13Y-68()}6QEj)s{|EbvQC191jd zh2SDf(*fDvKOw*axLY42nYB)U&P!eoVDd-z%*-l$XVhJOd(xSJu0kIjpLK7TaL)3v zIhl}?*Nn!j0{DJ9;U3G<)m(~SwQQFzl%mhNIFYc_oQtT{M({O)cw`zUlxTz`*m>z^ zq}M?IM51mu-HK%X>8G3#Rp;g2@#T{9vkkMr`I(>$hGBhmGJ2Go5*4+l$`{^MBDLx? z#<9TDQzPd`jTVZYQebHRR;_B@lTL+8(kPsI2a=c2Uq3yN6RjhzJKq*xRVhh|zXQ3m zs$Xw1HHY2aI+Y%pAoqUVULceO7XqKfkwHr)FM?P#`a7|oz5D#1Z}wLP3mA#t<3cI% z1Y+nZH=wer1ZAE+qh1$qNKbI==N}|}-Wu|>*G-8?!h{ORz1kR%poJOJcN6X&Bd}Lu zRxbSO7EV9&n1^=Wz9!spz)Zt#`(o&i79=_kTro&|*^U1iV@^!lnwqidrc9tb!~5FLyyXDA6WSbqg`lnQVtwo%?6J? zfbs+kMxl3L#`x0%-H=E2pMp$0~ zC>FV$CCikWS?mF~s2PD4;)eCo{fpHdw!*F#wx4Z0nMN72?k|s!uY7@fQ$p3UCXp8E zh^bsR#@Y+l-uwmo`(6xIWW;izuep<)?*TWNDFy8I`MKfXFFmqWb>8OQ7&tw`CU=jo zlPe{MgEE@U;XpmozTVWAIonaFnURkUW#OA9b#jrS#HFg`#bEcr(_)XWd)9#3P*;u8>2E&7Ud>uk`VSBPk zhD=Or>M@4?)pje^^%XTJ9W+lV&)3!skFkMsTZ%WZi4a5*0czsOD_RMytA-1MzW#ih3`KhCI+uQ6`^^xxec)?znTSzi z{iiw6XR(!y{L>Jf2hgz==m*EE#!@XIR<6Wf+he&L6J1#Knwzf7G1CKg$jLVOoJ&Wj zsbuM#i2GRHPS#&rn8>j+mFa>CJ^L;IySC#IB5ZzEX=PuptZyDfNzF{9U>$S+s*aT5 zEo?u~D5IQw?7by`a=t)c#in?S)Kj03$b*RK^sou?^N^$>?Clnjuj>7_(gv<&Y20_G z^pKITmxf>2B!ezmFjmEDz9?c=X1&;cAkj|7Mf;|e3&MA^7-|Zea<%oO1eDWp8?Gri zA5FaOS64_~R(OVrMEQ={?)xg)L&y&amFPyEra+(Y#WVT^+XopnuezX5Jr3yNF-`lj zlSLyzNd6KgQEtutP7F;g#H{(f(r&^90A!roM02-Uh>?cH}z-?uV&-~9xx zH#y796o))#a;53M!TFKt@v?unbH>SE(o7595<36`0-X5!Y!LdSp{caCV3WGX>wzY9 z?NSx05|vpK?zg>pW$6jp6btrr3veRRu1VAv8)_B8G+LRcXKZ~$EBIm(HT7aWjQG4` zha2G$g6F^Y5h+pZc4*(JW>;WI)Mx1SF-2oUOxk++>WjI8#B?J)iM1vTF))c*t~Be~ zN5d~JFqb(wW0UZP8B?>QO=xkZNW>D31bqk($*~{^MuUtIrw_HpUULR(FPnCwWK`}R z+H04+UN*U6F?mfqlTk`I3SKIN@zJche$V68uU7@KVQ>%#8sfi~Gcvh_b?OWc^Ehds z`kMp4C<{uyk~CAlfUmQf-%k}nUe&a4^M8`Vb>A}@Bi&EhS|2rCq|?#?B12{B(T8ZP zTV0|qqNkDSJJ}O4GX|(hZP!`3mpKXK94~)|xZc&Ar&pXY+KX$4UObFHO5aPf$=Kh<&?-o;gxVRl5TSDCn0I~TRIoV8kLx#JBEZ!=Ey2yc2Aw8XVxt0 zP;tH=7kt5s-$t&2R{$cFpb2W)Z+{y1zput4{fy;w46Ubh97|tuvy>YS9-by*(c)Y4 zhPd)OdvTK)ILgt@ahZv0b$za zKX%mqVNU&vqo(R=0`PD&XOy$Hw_#K`S-FtZY|aWgTna}lvIb2D%<6R|UK zFfgaTdOf264DVz*!EdO2qe}!UK7jx%-Rs0`l<@~q2GVXtbru>W6|JM~% z|5@D>qr|_Y#{8donEzYAH|~E1$^2J&m2CfR0{<=O??3S>*;xO{iTn4!H!@D=ZOoM~ zCPLediNU{zZ^lp{M;?xGuv0DYx5nO54ehr&ZtV$lqi&f*f&%JhPjZ4%8c$Xb!nWss zELZgDFwUekYAqc;+1fk2?uH)_GqKy-{>gjG_5StoH(-)Mi~L^BQ=gjOMMh0-n!v%J z`}O6i+HkVTS8v*)GSuVay1LrXNgJBd67KWZnZkR}aw&3bQozgOh)=kd|&x?V{QN@j+dTyz zll&V=@KEH0eoX(`-Hs;n0fY>={Hg~-5rjzD;d&6bC#|e#p;u99Y~U9-K|pMS3!(8R zhNKps{W%iKo*C#kxwMXFP}jJf9}d;+z(Mj(agdTGnG#C1I<8@`#t$(g!2Ej8{UjoP5A1hJ!rh9Q*HA`aZH zpB-+sp?un6r|gscr+M$iJH^S8;|rj(rRJ`ikt=t5qDs)m3v7|svrWI{#dmwcCxr1& zq+owHt_T>H;5bvyZmE|EUXqw|pGjv}+1OK!hg6(;PEc8p9dAY%hPW3O(%P7KR~5W| zd^KtcL%uF;8eA8-jU^USKk(+aYn-vV_@f#P{#w@MB5JpMkwVX=Rzg;iN>U-cnfaMg z9@gDd1L3;5h}n5!uWz#2-yEz4ZV>$lP#Elv%D&*&Ag4{SoKqXk>>q^NVh#pO?_O^t7j*(;COQOBo+$jw9oZbyf)&x9vApH1krv_MR0THJCPo zot1b)ffaF}0SqTRoZXR!ty?j$pJ&7K?&P*{Z}ln3V34J04Bd-H~+={LUCW zP|Ca8vryIm`?`dN9cO|<=>j%nE(()A(h@@~Y@Cc`~xs2m!9Q8S^@QWR!@ zCuiSsa=6lwR)r%zbHCUu*DbrFY6p@HF-MBXC4eU@&>Fc%e#dfcKVb~v`7hm8m!bJ$PJ%*C|)Nh9P$#`g-7I&}APei$NJ6DHU01*uS|+t~ng-H(wq)!~YE*N<1-p z7kZ{8dce@1V2m^q&92OFK_f$Rysn>gb&)bmL8{i0UM*(}KS^`;(8!THCYoszBF3|FggQ=zngNJrTwe)Q@DR1 zlq>dTyy5)S9BHX$0;4o6Cf2UiJnF{ZAt`Xi-2!-^m)R!J3hxgH%5`Eb!6|B{_n{tP z@H@OK+?a#QASx#Lor>++w=d$KLv&jhHge0Q_Wk)bz#@`83zj+vG#Eiy zC8P5hcqC$k2FU&r#`fwky)h-ht9R|o3I+y@w3)Crtxa|PRJ zb+v)<63_IN--yhue$tD)kj<8b6#fo(>!U~^@|ToJ8X9^c6d78NhnCfAkZc}F8A`w_*~FkJHno$DQ(3;+=Lft9+XQhXuBBWu^kGNQG5xwF7(*TP+N zTMSPZ=t@4H;@QP)hb+|FfQ}^eK45&8XOoF=!NK8{mslyx`Lp1uccsp|*O z@{e{3Ss<4O;m=>7v%{&|5q^mf6`(6jdPli_HgjOE!ehWAhDgiPh~gtL$(DLthT5B% zcNRYACt%I15BJA4v1TfgG1iND{VaA_SpD@eyERf4B_CP=a=p9o>CS% zX4lOiXcE<;=XP+%Efs)fFX2g^8S1}CD3j?DZ^?zROSa|D7T6devfgs>stCe)DA2D- zq4DTToVL5M?9KIsj9zC7Sek{7h#o+s$pA7OXP`D%y z;ss!Lha=M6>&8eTCkmw(v?RQstUJNlCUXlL0TNEWTE1he0zFoqljC- zMDu--tpuJc)9$|MgO^6zw!ad#!U9G{lbYbnO}SL`O$PG=QBa(D$^4-Ilpz5u3fjD_ zfY0B0uZ!XV(kSk&-4cX}5f%4c!2eSM_Ik^Ns^4R!z7%?c&J-k|ZFC%(H_BxP`Lv zx-LXpyZgLot3=J4x(-+o)gk3i2S#J9e}>^Dq|~)}lPiu{kgyPnAUUIY@A#KavYG;7zI*=^<; z+Mz!gRqpzi5CJSFv@@F}Gvrzxcbe2PYcTv+hRn#URu;4EKl=5DAW(qidbdd0ix3n< zcz85{y^2qe$(av`?U~!;8ji_H$cF_Tawoa>%gr-!hoU&7S$_{+>A+|vrsn`J0o5(`ir=-MhDE7wB_wLp2~fVxLTa$yT%O z`TM`OI0Xmh%=FWC8}TaWV$Kda3n!@=5!E46fxRsSg5t6SVz11qpEsxPuS>KEGC!0| zXq@uUHX+3I=@!%Z^98)7Li{^f!woU1Wx8VyWV&Iom#G>FGlgq!Zzp@k=-|Grsgarr za@$xc>kK*eFxxgQCEK{oGFw83`Oufo8w=xp4FQ66m!}Ow zbLo50b8(Z)%zFOU%BI`DeTE$5BJv(c_Vl|1-&|o6C*sR_WlsQ~esbn|;XW~MKI_ph z&PqNRlVg=YIqSruuO9S)A(m~;5rm(gXpqyUi1Vc;67b8~r-~{CkiJO+C$`1nS>--pHh#+m))uTTwmBnW4j*O0p&SPmKg%y9oR;|(%FzFG9 zWR4M#1h;)@`pFJ7%j39~LRyc{i{Bl5Bdlr*sGVVE)wybG`4dJmBVnhG!2(hG#%{QV zx8lIQ7dbheO*=BxEb19i+DcKKAK4%=9%^!>COF`V%Wa1$V%Hm9={Oc1K_OCk2_$5G z0Ev|_*kbbFs6rFS#JbXo%O?pY=eC{l{*j%62jfpmYzvJHL?(8MRW~DpB;z@fbH0=_ zHzS`yR^t}pIa+1f5rRk){aF?02@=>()okufIVxS4XbpBVdDE1JcmXqi^cyDbc2pS@ zGe+2w+|G3j)UG4qGYHigC9}N;=W2W=(UPcfGm&KNZ zp_|8#6!Gx?1dp1R5M6KpLA@jVeDFjmx|R7rrt%l5mys`$^GN?38AS%!w&uhLzI}a% zr6`FG*-%t)7hG8m9;El2_i|iq{UOlTxecj%W)jnuZpxr9RpgQ;W0{eA6N@t0?2v&O zKNy3f6dHSFLZE%d(7?YNIfTfPOs6k=k5ae$i?B<^aqT>Nkj^jXrhpT8b9F)l7hXKy z>6cAU|0?zTpIybOhiGh-K#6@io!lEhR<^{htLVUAsm z5k&{{eONk?WQDtde`!7DK~${m{4bjAY+oOKFFIkUo>O;^>W{pO);G@#S^?isagpuw zx95oTVnXR(SsN=vrc8oP8dJyyJIY9W4^x|LFvl>AJXX)7T%_*{2njwtjW1JFE@VE)J3i^uR&VD=?J;;Y!v6Ql5WMEShW9hr9{ z=s!XF(~gRk_|;MRW_s9st9Pvvp9{nagltd#ih?$q+xsO-{mt5V6nV$uDwZd3P6%6T zAmx?_!5JCF6J035pJs;iiZd;Qn-`2X?JFC85|vje$M&oMuj{SAPT-*A9NQRNrf3Kk zRh%8QBNia{8jLkcP1GI8(0@ExcommB#_(qOa1OF3hPjS8K&R(T`dxB-z`@LSc#YL^ z+TrXxu5~vCoSUJ20I;Ynd9o?c_>Hj!rX2SKqF_C5J`t(lQO~_8!!`jRR#Zw0B{xQp?q*|I6X$B}Tl@H;ad#-xqHA>Coo{ zH70wQQ%x~WN?gXQU`5Pv3U`pAn5yZqz#PC9pRrBVgfVa(5O^IhO0q*u{Ss}G>BmVw z-e_XeFLw4KKoOB2^W!~0^8h=JRR zkz3rek&t|b*y};UvysSv104MA*YNks`u@NZlI|yHw|$0n1myD5$z)Ht?=vw`N@DFZ1C)Q_!ykAN48m4Wnn*P1a`Al`#GSi1 zN4(H*P?aNRzjjJ<{NC0Q*KEe^(2WjCHfgiK@0x)7Rq~l39j(t^X;->(iLAY7TPx9& z)7}yPCrGK(bk`AwmwC>rOJ+`w-32AIS(tm*Q4>aw(}qP!&@Fqjd`|`((2sB9cp#2} zVPG;BYDY|Os`Za8r1P;lXkcK$ckM;wd%I!+WjX7807YDsL4JS6jzt2bTeY`Tj1|C+2O66XAd81aklX3@7?u$$pFy|HHrff9X~ImxdGlyL2b+|IKhB zCJwIuDV#{(E)loY>9dOFBUDs~E3T?aqkJ)<)q6HR z$@_EX{V|W35p1N_$Rb>NuYHc8l{L_3#tOiRbCP5GIedO@`x&};#mM69lS9v_foQ zXPtq$I@v#tdABX7x4jv0zDMn;%g$sWZ3R^Yyj=eRI2O zYwFA0yV<8>t1_2>mS)_oxQKQH@OW8ItTi{r#VVGRa&*iq8et?T^|+qOxTRQkDz_xK z@Lf{p>oq25cJ=vj7%AKTlJIu#_a=wI?brYHWSPV&+|Wj*DFSTefp8wx56+}T-4X{o zbaMM%vss{gNJhC}*9?g@<-q`h2uD}N5JsgTwb0YHmt?@wmjYBSE1XhJg#^r!n$4BL zOMS4jEd!4T&4M}si8IK^b&&P;tSN#;QOBk5+XcfSE&X@MZ?K6C4puxWCVR?Ok3uFj z_Qu3_!R~Q_a_Rv%+FDjP2V}J)HqzGoXoj%K{lj_tr~4WHWNlAEt$5U}IY`-QKoGVZ z%2hG9B{pNveOg9n)Yht7pSmjGlW;lKZS~;M>GAlg#53C*H=Z@pJ!ZKX_*mnf&`-$lT`F>xP z7P=0$tFpM&b43t*OAkY;Mrb*V0>hqBIzXkg|fuqJgLXt=&S`Z##g z`Vyx>>eCY9{`19xZT(Z~H8MU-k?WmGlNsZW8dsDG=0v}-I1>1l%=r;VK%rD{A(K>6 zDB18!EytaR!X<*78ZJsU6f`-@vTd9ekhC~1RGTq_T{-5t7T;d%gFTPw;)>a`Q+p-| zBe`Yt-$UvUbQNRtqkKV=TH=7DS8Z%l3fexxwfmFMQuCSNF~$5M|Ifj&gg(UF3CM6l zDL7Y};PU9!#0^OTb8VXBNV>#0Ssi2p!TiOwErn>J3zh~A!gA>8N|@Ktn&FJ95{ZTi zpbXDQCQ2I(2d5>DGO#b>_I3KX_Y$qtdFp2pOP}h3bA_Va_)QSFmlnUR0j8q4M~PL| zeTUYT{CBCM?RdhW`DHr>8`@pf#g}&(LZ0j~I<^+b{V1ebE=d3G(-*lt2VtmgzTsFe z^RB*^ik|667jqxv?>vm7r#}6ahTW1qfa0)b)Wq>Ji!jZ_NAIWLvvW9J?YL@&EqE;m; z*=1b$n=6KMY5FR@st+O}OTN|`-}tJjtdx|^sjZ9DQh^pN@3+NCklSQL&};iZOp@vmRv;_Hr4%#m{H*;dX`^JeYE+yH$V8CVSA)n z6AarpEVCmU%1{KEI`@51CYRd5+$A;GqVp9QvAcTndK~W~wrPeT8SE$KSj<#;3ZEu72G2Z5~BhF?F%CWf_@V_nRF@}lrNhD!dn zD|3p06DKnl<~c)yLXssji%1ipI9y6<6|;ynl6{X>HbABcKquLT5{!uI=~!&84c+;O zY*wd*``g`VWrPf_Y1e~plXHwx6}w1=jBk%$JiqRkp2SHN{`SLrKr{Qh+Kk zn>_(!x&?0EgN8PU6O$yhOw#Q8l5C^o7|DzZHnsY}7~K)SyK)zq9u73Q^OD;xJt|{# z2(qF%d|5VL?6aT}pJEZ;QQ<<3uoCoBL$ES*;q+{jkE%)$3;aV8U|0Ml!EurVY+Bq9 zzZ8X0=euK5ram6(6+iix=3X|9HeE#?7Hs70OQmnIPHBXhhfda^U+6S+j^Xg#A@*cs zJ~y3#p~WK_x0+y9c}u)qHHne8qrM*%;u55eLltBu=Qh}~)y!B)t2)~-CseI(4id*X zLQ?82LJ8_6Kl&5`*%Usp2){jdO0ov4c5W{x{WCUxI@4|bfIxQfAy(J2S2;0VCgWNL zI&^^j5_d9ZXi;r^NV&ZsLr!LIQZa+Vlq44?rYoC+j2w9pIVncveLh< zua}VZT;?IFU35mys~>lG1>EmGqhwiIFjXWFQqU_1t@9qkJRq$_6Idpx!?Vq4b=W(7 za|{nFuvq9SU*E37-i;Ht$MzGvZV$3$1?@Z4&0pJ)oQp$%G;$j5FszRy>}JOjISQzu zFf}oc_3aSSD-*Ppox|lWnHpYCO)2d+quY&IDSYZJ&4j^`Yrtfx+pf=c0Cv3&IOC0o zU%JT_d6b*|rN7$|jpXw9cDj43Psvp+G=SUbtmyv+(ZinWT6XJvvR?&(Kz(DDaP9eahk7dn*@@%$ALQY-^v16W;OKp&p7f%uXU%OOvua!hi?;X zze9YnE~c?h9ljR+AqW4NJ*&NIdd~zziM(9F{-6_%zU-M3=tMNip3hm+A=G)x1v>#f zub4iMaH1WUtIhkU8!lN+B4HE{m+zCYSO9h87Ld;iAgy9~&KYo<&nObSUB?{FitOT& z$A2{u3n=fH@mYU)1^%Eq;{%vFOrzheahA#TA=TPX1K+JDwJ1ESdAa}6BR$!ir&zxf zs2nG2QQ(=S9)&VWGxvkARvh}FI?%`U=_xK3N@emi3zZcnUWHyeZz3qZkpqsdJOJuA zZ0S@Kw#nhi)ea;+ph}mmluF!#N!!PLWg#`(_p~y9`06KRC&p7dAErI%LeEA2J`2W` zd&+vYE3=cw$iuH|H4O*j5w51a-1IflWck`4k2F=o`T%AuN)h&E1*R}CT}2r-zlR-4 zJ2eo{f19A};Sqcb9NZPAkn!l18y5v-4eljmm=DozQHjmoX-QAmr}Gb)#5DOw_3K`Y z-4POeKl$Eb4U*@tv1bPp0t`541;!ZKA??GiVjy9h6ySE$%)n#fLG!Spui4oo5V3as z%GK+Lz-%ge2EoZSEx^Y;0MZ~A*37*==xIKCB;1#n5ZRG>w_^_zRFHH#!sw%u+iSAU zSi{TE+WYgD{pZiS8S<|}r7&_=MmpGrwR>o!<*;=?@?4SPlSEmx^SE=y$f!z7Pfo;x z^E&<3jMGE@jb5qNiGDLDJ*&;*{TUa1#xwPKANC{2&VTIHrd8r>@!VA#IMzl{0Rm}$Xm`% z=AMsEQM`4knEN3==NO2Meq_Sb`yU-_GBP{GmWF7j$?oAIYbc@K%7vO6h)TbJfd1ya??vSO869 zqC-59n@*^wSy*xD;1xV`HL0Y*dy9&eYK)f z9+k1qw7%WjR27!$(}=^3cd0Z^n_GM)sy#4(!>STPPD-jr->vnLGIRN)j*hiL<81;O zmqeIhzncZ1GAv%^vSU**_o}sR!PrsTm}uo4!#DP51TjEuy9}Zjj-Z=;voDRmDG0oHTDex;8$O*h`}8{N#C$8ouA zejB+s)^OkncLOC6ubl3}&ZL7RmnzlM=G{YN(*zW%v*~!fVs{;uvch^ zi3p;XIF7=~4!t`5IFs=tI}cP9WBL<;zdW?mu?%&8aLpjYQ%J!_zdX)22g4QNj`v>C z=joUSt?PAb%lb=al33O)v$FXx>Q~wNmge8bO5w{dA)POreN)e_0c$U`Jfw`7$+b1X_8Ht&pC@MJ>`~l>(9NA8gygp=`ES#ggJ-{Glmecmu+i$|S-=%NaI0k6}ZgC*mc>D+W+u{eRjbig9` zc4+H|AVCX|dD0!}5?fYgFgyw^=radU;P}dqo5hWFTcK5MvavtjuD)66q$^Z0)hM{` zLIDul8mUHVo2)4Q;dL+H(HnYj2Uz9Maol0Xkla?+*{;wnhgpZuU29eA9{-KEw*Zc$ zOR_b^OeJQPN~{!fiJ7s)%qlT6Gc%Q#nVFfHnVFeN($n9)v(r60Jv+NQ`!XXeEG@4^ zq=lu2o1g3VJ8h!Ijz3W^MW{YJ?Y~y~bcE&+7#zswI3xOwcM+Py)~KCKF|4Y0K3~+V z3^umu*?(D{Zc3H8T75RxjM{!c)9)~Om-K=@y=fgz+wQkyEH6D+;gfA`mdVH5D;V@`1op$+*TaHz&8`=Gz5`Rr`Dqbe z7T&?V;oq0-RPobl&75iZ5Sf8)6ah~9IA^7&t3 za0@2_+-HXc@l3W{2C-0x7xao{`_-7Vi>LVKgIV?hUm-xS=;x1sYt)^d;L(iDuq)!N z-hBx0y84%jM1n6fJhl$J%Xj^65TbOa*iFl#gKXV|+>P%^5V;=Z4fB3;1Ksm*8``2r zHhKHE1baSFm|iCaLfZfm%PKLl+8+?H=g9fnXs*@v{fvK$ai^^*Y}d7FZW9eBA^&~^ zI67kOSX^6of9UNBZ;>p)QdzuKORlHNzGLcaIA?!Yd41OCJrf z6)#fd&x}1bw{)!ctLct}j@1bF=v;6Yw zc``OFl(vaHnU<#uN!8UO7M~daEJGCrzbgOi9aeta7i~Qfexo_i8!NUeFWJT$A!C+b7Ve_EDrXf-T|Y}MgbI``sKlmAJeSROk@9D;nq9f~PWgN?St$I6U$6+{}H}skun`g|4ftFr9l*8vBAg5f?sZ zZK5zU@dQDO7UR*GEL2GchO0VRQmfAYI8X$A-v3ttW>!i5@+Jw4Wg)2U0NPk#_(3oC z)CEhhXJo$ytMigX!9~la9xF^9bj_c;I?|w_BhV#$0^jfjqK{ADcx*Do|6Ex8-)W=r z?;`5|J80Q|ui^iH)H3CtS9kn3EmIho|F+QL9~%BOHOg?s?YuCKdGF&-m~14lTxHAA z<{e|ydd|zyc|%x+Ynky=X1;u;2R}Ez#U?sZm6b8JokJP~rJB7enTQC9c-T&~w!fa- zzCM&Wzpw2OZ??BM2zvXD(bMa2hmGZ#=C`EiVq4N_kBOv=HBEiVr!PAP;bYI0U79Mq zK`%p}H@D!$B5sb~o)L?~Vc<12VueXo0LD+1p4w`@0*>^&q?o&_RpeZdXWT=gwAy_9knf> z^8L@9hWXKuZ4m}6NThQdxhfX8>}+SivI^x&$4aHUeW^OLK;=@0KW2rafrkiO{TyM$ zm6;-`#jHYBOi8%a4s1_j%Z z22d}DS`1WgS#J}Q&5XnG(Z5&8UmN>*hnQSd2Th4e#*laXjl`0KKG)W+$1e@fS$3<7 z_g$OSsA0CpU?yqXjEq&@ zpZE6{{5rhJDND+S1u>BbEkK4_4GOKkDe@vH20`FN3#$5~zYQd?1*7(oD65Sse`Qc3 zMy}Ygh|#wHoK|BZ$Rd$Bsl(Nvj5vR>=gT8#>z=y5QS)^nbf}VCZ?j*w_(S#l`>hF4 zw^FBzH|6YT3m-G50O2QnLKB(mb18TI!rFv(-l@|#PA|m>@Xpdrs331l8g&7oIncV} ztDf+{YW*)hRk0*84Iz&j>PP;l-Ub?{9Zs+<+q4SA8`bMO{nPg9W70oJ-t^P%n3lvV zRuW+_3J?J+g%v9xS?PFBHrzxbr@(5Azvtm zA8N{KR$DB_F0M3Ph*~H%-OeY$+A`r+`Bd}ETc1I*mjJBSt4e^rD+zM*{X}3$K4)M&l=uidY4Dtrl8EZ2kfIC6^Ye!1a4RjzauE<(06ZZm2cQE zpC&Y2$ocdTlDxR?N|YI@{N5?%vFXr+UZ{!^RJ4X>l+0@E91;jfj7>LBf8|lPpM|C6 zP^A$~Td~&9VdWsHANNS0%Az*dAW8k)KL7}&v@R@dQE)NX z1b?J6CL=4lMkw0RF|g&d3!Uikb%C+0D+kX^SHdPMMU6Hq)(-oXxj@@UH|zBzvn*s0 zVfl+;*u}Xgd_H^+ z_wo!*4l%_@lC!ENgsCUewd5EMC&_%`F9_lA0L!B?rsl$cDX5%QP`aBMM|JZE91njR zY>A=OAR=t&_Ilj62Raih3dv(pbg8BEJ0GFx(ze>^acXjsLI1KyufA`>R|vB6+i|Y_ zI1nj<6J~ZU?57^)J{_ol^Lx2FaVh#4>@PH9(ViJn<2B{hL{R7msZM5BOqZ~Lx*->R zX)%_qd9*GvGKg_1lc-!hM)cC@y%PyeEmo~@2LRl{DBJpCfL$4B}6H+TQ$3?38yc^~L`Jgd937h+dZ7Q0KE*DYBOBU5Jk^~%BHLBH2$8UH zMU7mXBBfnwTT9uVsD$~`6&o$%)y+^&i^9CR?sH&4gdYn1GV_9>{Fbv>#EPQDc^m|( zWZ|>B1My{x6vC$(iReH4`GDeb+aC&lQjC=b@iNM>r`PnD{_@{uC5Etdc>X<{cVYJi zZzMv)ScYyX7~|J})D0$^3JTGVNi0i)5C*5Vqhf(3T#NC;jCB6Mp|S=z8wefshy|(e7ax zePnsb88wtV*dI3@n9 z_7VhQC_s_c5_m}#mC6SZ^oc)S_XzptA`h{#1WxA3mhSo1aLhOR;K`q9>_~r{HLtcKoQ=^R`p6CY1 z?)TKC{yrhnwzO1~V3n&+Plew;q_Twdyn^Q|L!%0=vs|NU>!k}e{GKJ|xKr6(N=+?v z*4c!cYG)UjM-qZcOh?7#qV~S2bzl>M=}21r;U`1pul9A!vrpGRlG3Nki5qKXtGX_- z)2hO@VAL;l4XoO4MFwS0txA32$%M9u)vKpC70%gGnA+L8c*+Jz8{0TuAA*be$T> zS3M7kSS7qp!Y)54?K72snnFCBBGQ$y#q7~=(k>a%U%3G@ouHgm%hnjQ9}6j#PL4Ax zb`>fCE;Q$}1+z>aR|{GtFi-uw0=HNtab5Nv$V^+CTh?%wiJQ>(2Kk}?`BO!*^lOr; zm3DD+?nKCN^+|fP&cLfOAn7MZqP89*sY7EQp5n0FNKLy5eZpMFDCtunV=|-nY56!m zUs45nD5s)z{HDSH(_WjV1bFfM8fxTxchJd<%$Yh>BA324J0I0U{;hrKSizNo^rR2w zO#A4xLMyqA`?10tSyIHyDEBvVAV42#y(DE7m^#{No7qk}=VtcW z-suBn*_Z*0$-`1y-e?Hv+h{?IsXaO~A?`#-MOUGh#gLM*F{-=eVQDdCO6;DS0)L(- zNwtir{GLJa_!TtJ3f(M(8<=hNNslt>B!A5$k0lRA+@7Qv`1}P18iIH*Mforo;S-0$ z5~)$(W|}6_40|*(EiGM3Rh}m8aDP<^nYt^$kWIy2Yz;AniLRnS={_UGSRhKVmJVOX z3Qfo>?Wm9QWhV&ndyqcMRunaPq+CbC_VjL3f`&Xb2=VBJ@3okc z?SVg4Oz*Fa!Y{sTvHBS*3#9lPwB?eUqbxtlehH&oIe1k`38k#%cF)O0Se2qcFzbAw zYvVv+d*)$IQ=oS%VMvcg%6EL%;qMa&Q>N61V?Z)o(T_!g0TCrxKzoM36&`D_JAARG z{3e;_Ei_a9VX<+g-}Ejpj3bD7M_jcrX(y~_&y9a$9h1WiiHHF42$WMzII3Yw37(Gd zo^@IuFat+g*$5Ns={!7cm0%fO;OsEcQ9X@F{G>*(`WzuNGd(?g)Zu8B4>sF5eAQ|E z!aBcZsdud8x_w$V++DnP9Y6DMd0`G_N`y4Lc@HI{uwno)Wl}e>$PVbGS?>@+%(ahn z^fZ+U>2Ge7W3O2Z2+I{>O1Y`x> zu4u2(rf-HcpPH0u7Z<^S;`axG?DIMskU|HWH@iHiz+BDI-9ns-ecxYHXybT*I#Qrq zmw(RKhs}B{jY_G~XPD^$zNBMTpA2DAIB9#=tx>ur6A_04%(N>La;M@HJM9V-Ol5c1 z-@j=M@P9mxY@=E=kKG%%vxJZZ(NJyfAZYG1h#`Y!;y5Wq-HI#S4%$2e(LdhWSBS9w zfuzP&IKI)toeZ``S~N5gj<3!MQH?-RvLA)@J0-PtA;q%6lx zA~(~y^nenYK7Ay(0Z!^&-`-UURW09~@-JlWLDRG6h|9KV;5$7_{s^F$-39kNy$&`^ zeyBVL5lsO^8ULS4J^_z03Kv2iu6_R7k%M;0XM|*rC1QTBgDw>SPT@YAI1N6Md`VIF zR~J&c%#l0%im=82c@$og^06@|HPE0i}S&ky~;^>_A9UBH@CC^DQ#7&T!{s|j&>$6-U$VSv(U?7Ef z@;n;7A?6mP6Kc7Urk&`IAF>E=DPYKSrxo*!Q)n$LyFMCHi+Zjku$My4r6G0zMbSiC zubB|x2&UQ(kZ+)G-NHX78A#PFox(M-wR~7RB>go^v{o($z{REEmu*6)8ydb<1|z1p z1m;NIWwfz2kB^i5addf1TmtGT!9ki=R^iNhj3R7xf)sGC(MmKqnJv)c2o~fXR#`vJ z?uT}GF^;`#g$jxTn?pP?#c&s;)-$u8*MBAlS)taz(a#2;Nr=a6b*qr`!ar&)cZ-Z&ZDf6~dy z{P3Zq|6^bqy;lY`b&^OdntCB$xQ4F+!sY-;H+y9Gd3|sKZI4d;P5_Ag4Ee0Gk()|~ z<~MBA2=p~?OB{K!Q@eI_BamR(t1)S<!AAWy-8DQk33SxxOmwl^Bnw9I3|NB$(>lsT}Ij+r_y6Egtmda)`H5XlS0# zTAlwnJ zl}thN^O%^3S)oc(#Whwj(OQ*Wn@d3ldXciWzhQCz5|0v?cH&sBFUvlaS-||8zNN+P zxqX#2bNX~;DTE2kF--Hfdh{M?kNYj{ReG!cT=)GSF{_}LHgeG8;2`|kzU(8u=q)1F zRu0_U^dby|e^+5e7zzLG6(Pbz_(kgZ8uX8?w5)`Tf2Xtjp8yzok$+Hzg^( zjK0n&s>t-!miup)k+ZWlP&9JTpqCR8p;t0;aiABs)H5*>`1?Wd?+0-$!mnTS-_wl$ zM-46hnX>cWGqhl0V)z?d<{va;+%>Dc!G{f>Pv2;T>fEDoPnabiA&GQaWL?D;Q(scV zK@nSwiPaMkzSXWP4nc)9q5`Z*5aAhk{TWop%O@rk$EVKLuCBL6*Cmznot)KrY4C-+ zi-|C0Y888B$|Ca;Dr(mWrH27H~YlWqLh=QLx-kX~~#Vu%PeV=?vy$f2(0oSln!Nm(+e((1Bt~fx;Y}r)$fNa+uYpr=qgU4=J^Bo z3=jYG9_Lkw_HO;k()(N|cd_?@jVyeGpI28cs9_v+MqEZfzrBEL>2tt=BYjn>0=LtH z8P-Ep$jYz~cP0r%B-E&JzJZ=;jU`w@QN}gI|F#B4!SOLJTP_?y&gEO;6dyVfM_}oX zwo4!)(F4)Grg@hTpf!^6&O?Qf-#<~RL|+0A!Vd#6^68AkN5BoZmx&^%30bhc+V_4h zbvw}#%ccm$;*X}Nw9u|uy;h_dez`e(aPfFga5Az@h19bkr@sb3^K$!l=0fMN z(<05wWWvEqnpdWxa`mL>RxFmYq_L&5Fj;&L0BT%Q^*~55y%z-OXns39IcQPG)mHId zp%?ufX$ovY85dNa?;5}4dj=Z;6CPo%yoQoDJkf7D{v59^^&^#BvOw5s9$cc_l>Ham zH1=S*)v`LZ@fz)3Zo$*OlcyWv9w=8w^ltL1k5VMoe1bwSog8G4k9t_jm2#N1J^V9P zZb@J`(T{w>Rr||!(s68Bc_hskmS~EBtgI8yk%~&Mr*9YqZ`F2B{-`Hu7yW5f!kF8g zZG3@aw?+V@W)N}UG5`7o1@K%DF^!CBQ>U64=$5|WidBvv(DrV-d?XUha9(mV471f> zAq?cHinZ8F<8N6|sEWH4cqEc-T*dTIf-sW9^ss_G-DxJ!l>Xjn?c<`E8%T# z4RGwu^aCzvYufu%P4t37G89evT|r{QJ{d?|s^NBZAZB1ioMCrW;T8UIgB z$Jw@wUZMT6$6-7w|Fk7gn@-J8VcOFIjS$kz@+19D8`Mp~<147wv774paGm4YT1n}N zWIjVXy&LZgp;7wf+W@le^DE;14l+rIVs`5hAg+4#uT@NA*i93vmCdL3-}52b=tEkh zsY~<8kK0Hs;Zn6BHyHKiW=HRVRZYb3Ks$=i!ws2+11nneVOSIVQ-!Kzcc>{Zfor%J zb}2?8#qB{61S>*YdYU^gslW!FMJ!1s`v+eo3>*;Op3yn9=gKs#$+LVbOo1~zJhIXh za`@z-#3V+vN9|f?Q)vLA`i<7z?P=4j&u@~CZkNMD`vmRGG@seuCv1l?sA zzVC#oWC{ zBfFUko|0^Ec{PIe(j9@<`Gci{9!z#Ua3|_<)kJa#;vqrj^+gkkGk8lu0$!K9u_?^W zB~pKQ28KsLq`Hhagfj{ATLeH2 z=!dU(N2b15=l65XBX2Bc=jP(To7Hytziu>2e*0)GYTBaZcO)=PrbhYGDr$O_Ozm^I z1pncZm9UUcl@1^VfOBDtNSHUes{*%~)Pj4@ka>Cge(UKHZsfy2rN&qJt*9F-4qZgD z696ayd|wgkjTv7zM}{i`L}pX+8=k8Y#ndQjf<;N&l4qBdk*%YBAJUkWiCkxF6XGf4 zNOLe!ycSqujZ-L!MV+4okiknk2@2;!mb^H@K^KlQCjerXgz)DAy6Sff-0#D7ST{Le zhBN>8XAzQZ2uhoGlw=to+mI7KZJH2FIwJxd6HCvYDz~<$Z|^O?nL7(}TMz5AY5~oJ zvwh!re#>xB$d@wjp>!Iv0A^@Hg{K%}Y$uG84FtB0mIqRA%CUgfNKfcrV3;xzhpI4( zLKWYYj^j;6bi%e&jjUh6ILjn3d8ZyyA24ZWvEa;XJDuthy$q~6ULN^XjKXs;;9`Qi zu>GxTWK~<%ct|m25hLMSo6=_m&Xfe5;h94Vb0n(fTvC-!;0V-p&cotF2*L|okfPMCs@JN;wk#>TyJ@6z`ztGIqc zNFhSu3PUNm;36LR`M?<8QU~b5yI8RD8!}a&+sx9t>gZeY=0!|3DGbTC3qys5K=}Zi z;)*b}PrEi~`&=^$u(h%qLlmp^2o5G@oSo>Hj zCmtkI4IFq8X1NtSn=7ZTLU&J1OyQaA49|RZx<)V`>%?S<%l{s&s6aUMzOIu~j2CD_ zvP(Q{(-?x9)Xhk+IpdhD9)k6(ykpG)gQ3xxNhArqr6a9rh1NoM4+HS(0#^@5m;YOD~a2nw|-8;x&LLU z!-^JwXwoxSSnPnWtdy4wF5sClc+Jb8kE|e^)%+%zhVH;tZ+yh2R-^Dj80GxfnHer z`(1c$^2RfPvt0;A%{(x_(a$9uJ*9`uQ;Vi$%RjD6wv#(C=7shi6xJ_uJooAK08K|f zJRYfOIIk3Nd$(*INm&m*qHqLZx*w^DHy6Cg(wUPOpuY=*hyKxq^<uQ7-$u@9f%{3P7jn4r_+k*2q^P*}OxrHA4{tT_sYgjnCs0frdeVuFI zIMM7$qbM||D$Y&1HWBC)BDo{oqDmbWtn8O zrRWrG0y$9lMMrT(pcjK8&!0%WgRkP{~FMRo?u9a(xN+1)o4fR$r;2L!Zy zyqtXQ>GltY?%l@KIcS+yH*F9NkKR)nl((uZ1)d=)>AF}bJ@xNQGWn0MD$9y1#Gc{5 zB$i` z8D=(gB~Q`U#tux*V$dm>QmI+tD_;2BjOlg0QFepBqNd42s1*L9ml28&9Z<4fML zeR+FkXu0I?*78fA>Xqxe&o9u@IqNq@n>P#%@q57`a}sfNcv;l1dt1n$&0gbnzkq;$ zVWX-ddS@+mjX(38W)X{lz2P3eeUnqUsvW#T4=R}hDI9#ZsSq!PJcVBodltd=u^;TwWF+3-m9 z!G*!?Ufv3gq6#M8nJS%q;e)n4a=kutk5%{j{yZ>ud_1F`|2fH;GdzkeGIP38NBh5xtxoL7ac1%~!nt?LUFOBltWUF8`VdwS>K-sZvxt4rOiI!??E6q|( z65mj7kP8E771zFb5bYS9jCRK1F9yIRt&Bg;@4Ld%Ca<9QQC&pgph5h}+7W-=1XLVv zDD(Q@I0XYlvdBkMdC6=+WqzD_I8?YcA8YWe^Z5vHpv-=Gi9v3d|CE87xVBKD?Zff)$&= z%6oEUN7p)$o+1`7zi3WTV#nBo3V$D&+SZjv3V;yd$mGa#bT<*@1hgBP5#|O==0W)h zyPnm>fuj%5g(ox|#Kh?@QBKPgP<1)NfCcoIOu($ioDOAz3GXnLP`)VZg9X&Hge_)6 zZ3Iw#WnGw6%0WG0W0&|dQnV<&4Ca@il5|R_ZlaB*2!Y5QUn1 zR^)-P{Ava_(*-K=R+oc!AfyG#h+}6#%Q-n%=atef8e@UY&91mpRT?X;Bv6HFjVkc< zC98~bLoFRzk3Wx9Dt2X;?>QpkiO zJkxyyo=1^pRt&?~|78yt$Ae=`J7#m=LOaGTxe-VmhFme%fWf198H|6P)Zblp_w{dX zNJ=j6lAY^g(k8&)_{9K*oRj$D(>5(TWPtvoN1Ajg&R!fM#>6J(M!2@3C_?hGtli#& zQ@ZMh$I~CRs1SJekK6a7r;yGL%y;QLg6jLKCbXQTz&aI?yti-{Z>evkLLzGqT;?v$ z*+?bF7R&F#+_4Xg6c;!n)T)xEyUoAw}Uc1XuJR^s}31aJC8b&Zxe&3x-*{ zdIIZj%pl|~8fpCpPS<@IWi?A|iydPdC(aJiu4T&|>r+qWYFu<_cskb1$rtubDU;Qe zDwS(TG2~S0v!BcBKWOXzY%M;BI=Ap#%_5hLzGI8vvs0~|@z&=X#P_v1na4TxRgaf$&SGSkI z5*ESOU;y8c0qxCFYJvX|C>=_PB$+8(rF(cFv13r)HjyhcT*fKW-T(f6m>$!+c4d+C z0NXIh_Ca(gX?uj4&GWRAP9o`f=A4&vOSd@O*l1dwf{maQi`T13GBs) z&bsNoEEf>x?sxOwBDkTDal77~cvu*!O_`AXocN%p`}maoXmI&+_OO6VyQQWRQBEIe z00jEx-Hs+uEnS=v^!PEtiR*^D{b0TiJSW`^PGE4*fN zVa|!F*B}v6uwM&OoSgR45j_N=PYQYH%o;`)>9dp%$BvPFJ8*b+2_{8>;X~bmId^<- zK6B&_q7P2k->!Ro5DpSpA&_(^nw@(gJZAs%xnvn%K`fV2980__H&8EkdpvAOJ!Wk0 z)Tqn*B~8Z2RUPsK^XGR5b}HW8!wvH{&4jayKS#kUV$31ztYC?x{;j8yLSy-+GL4}z z_I|$tj5oFH1)y|$(_Thd2vn~L6lRgO+bCDUt20dhQCKWkG*K}1 zlt~#@%2}N>Wmc1tf&0OjfaRCm(S<73c!T{bHxs&1drIzNAG9sYE5J%@dbIq@}QlDX)uR4dD%F*G|G_ z1V;Ho@=szPH$TsJco?Jik*RGTppl3EiYX%(P<&aH~|SfRK~tK2Lx z9pngNsnMmNaqv=NP*S+^Ad?edBoc9>?rRI0XI@WjEql0ZK-`XAOVGB{SC&&9GJxCo zjr!WSJt9w}gU^gH-i(rY(I6R`rAp3hjNYFH7A3bynmC*cPJ3b7AVd7K2pU+e?@b#D zf;BbQde?#WK_Ku9JrtxisWMa&ywYM<2*_?hSq4|VTg;fSHz^fqj?7?wn4uw-0Lmy8 z1w))y62&bR;=5W=b&R@tf+q2;sX{2jBZw{BDfp&>>yFdT2p0ypg`06GA{hU75y(P+ zCACe-Laeu!Ud-9bz@d9(hK9sud~&A~KM&zwo}hh-dGluNz`Be6Y@ zuDIN(D4Gt_h=H2pN`ZNyd-Z(RtWmc>TXMlh3h3rk3Y6awYzDHGkD}nf9hp#;b zf-@OImnQbE*URtbS~bP3)68pVTGlomcuEPin#Do@K?9o!!1Y!M|L-c{OZc^q9oQNfKOeiEP#; zLGuRKh|y^&55sFn)RsUO&W$123K^3L0Wsopaq&m+EQ0T%MrTyO8AQViIpe<-WT>S% z9u@vEinLnmK)o2>^WYv52hS*=w_BX?vj43fCbH9W0%^jMTR5M#pHKM)P6QX5xzmI7 zJp&J8P)4B&HdZpjp%FhKgBfXq<77t^&13mj){Mb?M<;5Jc>L_>J|;w|$p%ud-lQnK z_%w@7h;dlcIB8N2^Bp56_XTad5tgY!m6+tv`1T;U8j7@pEn=a&L7UDM#fSHs$ib*I z^Vy68L#4`Zju2gk4q+_l1%;pw{R-u3O@xVbW=l5`2F418o_awp(j|&RQ$z;z;~@}h zVWZg|fKe_ILQ7@THxh49`OZAD(KXWNJS`y_4Lf0t^H?sMYnc%#dSnsEX>EJ34CrYF>pjPXXr_c*75dMH}2`VDYCrL=X*SYFJ4rP~qNqfeGjwLbAFVDWP&BBeSA{H;E#!sP$gX zR#{(@QO(4<_|=Xi2NXvrVieXDC;AZ1W7YjYqSO%sJ!!(2Sd8E1!WwG8o3R5R90Fwr zm#58?uGEPJ=3$ANthQvx-0wx@`;4K~HTG`2QPeYbfi?cNuh<5dU4|7%eZi`E89$U7 z6&y)kpX|czzE6FRXwoY|2i%VY@BGS%rCo|Uj104&7b-|ri&vw}t-bs}uptCnWNW*x3!11XY|UgDtiA3 znFM;KPEm&YI1)0fLN}zdjtawvh>pp@?JlO1maonM(XB_yIi~$5AH;Wr)eoOMW{A|> z+aXL!Lytn45k>~@8=o5&X;t_|#Unrs?U zDtpW9M);-fO^W00HtS?eGHd6_fk~wsNI02OfF;VHQ7RSqopd!C74U;?uSC)*oEgMi z=FhL`v>)ri>L|0e>`lLW&Rk|N}eOjoOQ0K_rGcg zh{rcIp5l|j1(?7|V4-H2->;R7fAVoj#(ev=gHFr5D7nB4R0%Cxejk0Q>x|H(mYtZL zmPS1mFI##|*#;_pV@`r6tloxuV-qzndQePtEe;BLRP*Pa8-bgNjDk+oW#m{%4H!GK zO*(4bXm-EIR3;r)E|n#xmXRBtd46Xai^3+TC(&~}^l5(kaKit7;O^-<NB)H}w-yzT_9#RRs59;o>2d}^x$LB5} z-Y8z*nT+1yzCAfHCoWwvJ-!GZ+f*Qkh~SNOMCTFl1OkxwWT3ku-7%S$J(5oo&PD7r|Py3KY^*B05LxC2!yXY1O-bQ8fIciOc_Pg^8Jt;lEOt*ckq5 z9{8^nrmr#o4D7m49W!4Cpl;y&K~yhHUwfNI5y+N~=abZs_ycQ6lpkIg{^3=1junk- zIJGRrMg$jeb2HO|xIwKZqHFQdII-5|9)5U~)yQwZ#2Pd%n&*vJNMyuLvMs3Fr%yyC zINm+N?P;C<)@TL>)`1B*vYL+@fC(oL#A}D7st~rKB#7Fhw#UVrlb+)iYtVu zJ1^OTtm{BFW)eKVJbVhAj7O__%2N|W}O zlwa~Qj-LQg0R}32n5k-+dJIjwd;Z`TU8{U@A^MsVze)B3E<-Ayp>=(zW{)xwIdR3#Wv%6`) zMrE?n%XZ-o-4O;yg`pQTDzM$c?BYw}*me7|{@#*x`zmJZ{K>9{cL(zgcG8CTz(yNb zkbf0eF=??=m`;^Tca#`atWx*FXD%JrakHKPWNiF?Lg^s&HT@66U71Guh*el|S?*P- zsi)TSeXlzY-#$r6hdNoynjy%kq(EAHc~zOox=zxB^{RTKkWy7Wi}H&dRmGNaQhamC zEUrMcmrO1G3>LNJ%AZ0BjUmk?R9v!Im zFGBXOI{e=X&;M;9%SOk_M##oa$Hw#@HQ~R(3BTf$j)Q@j<*&!de;u(bZ2uu*GgQW` zQ23GBtLi_eMVp!DqFu#R^81(3q4-0RWr6ac5CTBEWT#fomQ{vsMUu{&3)ht!=H}vk z%CD=gHZ2>mw~w|uVXIHL2%n)IAEEHDNI>)|`nz+BLDJ+TeT%L;mGxoh9FQlF|!C?mf2nlPY-GVvS2x~BOR1mvN935pA&@z@iJdB3Z*iIR>I zjfj}1PYWBJa%@~`s+)%78S-I-#9q^QKDKk8=wRx8ANb+Uu}q@ZouUgv+evGEt$u^$ zag9Qtd)?06(sba+tGs#|i$Db!u)Vk1XQpQLUeyJwx&J!CI4P~{!`Rs&DOt~z12RIoK zMp4773ne~&(!!ASRgzT()yjdW8~;fZamXjcC)tMX4%Crpg2bR1tDd|$qHjDK11{rl7jBjeZH$zP%I z*EGskuiO89m@t0nYyTm$X`Dr)vcY|J&VC@?#Y$L)+(ik+UWS^wCax)rI;*2uQu9+k zY*l3#I+-+XrW#uL3i`!2r*CmK8hrOMti4()yPI%x{KV#@5{(b}L%|VnQ zkLN-!bSSKZM8*&pt*7BsK1VAyhOjwQ(E6 zK9Vwr3gsH)j!YS(`FzxX{y1=zLM5xgw4v$-RwjK9Qo_QNrvwnM0xKg%lq7uXK`em* z6wU@^LMVY_Dym6$u1nW=ljnBK;DxX&bwUo7x1*9&+}@>-Z+qcLR|7#R@dvQyj&`R| zz1Hlw>k1VVpb6&Qau@Pbb1{)pb*pCfTyUi60tYGW4GVlC_@~;7i<_7#M;pMp>^apO zY?kePv+%28srM~<{$aT%i6cqo*&}=F*%Cs3YEkr}-E;#_el-)qZyR?d1he}rA`INn zi!gy7+MuanHtlHP5$!ZaxS^S|Ng-vmic#JR??$i;$yxvHW+a0AVy>zg@2k0X1Pyv~xe9MtThiRmN|rDADdlkn%vbxWmCZ!j)X@tw9o1bXxvo(8=7{?nRK z1e6ZuAq;Lub~LAE)&3_Hr)M3-@DkcIP0Sa-!hF(#&GM8J&1>Q#^~FX$Z5uJmr;jB+ zCwcZJLb?%#TlN(YWHz3>+RnqZD*vDq+M9nMn5sFLvu7?wToA;L<|PX3b_tW-s}NTG zp!RX~b9KG^oS7fi)z1P@iHc!XZiDGwy+j;5JX$G+&5V4k5c!8g$BJG3+Wq$CD{L%k z;Um~PkEsnKiCkIL`v_rznqz5y1bVgmQhsb}NNvbX&i4AfA>eX0c&L(U z^!bD~@{N~LJ?wVZXR|-++w7YoT5AazXkiEvM@6`*wsFF)>dr8Mph3j&EHJ!A13V@( zW1+&z71(@KFGnoyJQ4^qh4v29C;QlZ3d2D?HM9`)a z%E@|fae|$(rjnfzs#cb8I+wNT) zOH^(rjAO}&%k5J-)f5E%&xRXu%P$S{3QzT5?dWEIXk1qJhBg(;a5oNj{gml4s`Z*j zndI6=88*O|^jhGV9$34~)z5pY(vpgM&#uRi-RB|%Q-0sTn_OXDC*v%)0WFw)5A(U~ zshkNg^S=wtE+9VKu$hUGa4~Dgd1iJ^RtS|gewu4L-<>}5SrE@&dgZEeKlU}RN}q(( zJE%9^Ty57{OK;>%sG@seo}N{?OS`e(g!U=-& zjlrjxqI@yF*VX;bYu|GC?Z)ato-x4AGB&k+n;#PZ%7&V~fQTI4kA34<^S_9D$2d!x zcU!Q!Y}@FvZQHhO+qThV+qS!^%eHOXn(Fs|&OPVcJ7;D--20m^nGrkmWMu4}J7Ya- zJqfgm-9c*NA#9yws(WB1K{Gzvp@2-UzX5;IE6#4Ma{xCh_2rDg7=iKPpsBH{VQ1O? zK>cpdVXR-CR3=?~Yw}sxP7~kR?abOccgu0D^VRhG%&Tsm&{`sNv59Xo)f{oTN$g(s&gbl95l zI+Uv+jXl>jP9xm!ART0}LHz~=00%)UmI(1xpgU3)lTQHWF~3}orXgJoc_JQlN~eL= zxaqi}H(`K6AEdjTtq_$#n?3z-NN_fMX;UhTPW&%$w?l+!kBzO`yP~MAzB(^*R`{2R z9W=Fh!G;{jlgf!(e9`M-a*3cpAbpS2vx0+k=Fe)uV zn0kI^2m6y8oiYmiPGFq@sc?i%RgDHc@pXemz=n2eq0u9WRnei>fL<0GFr)HFrC}KZ ze|(qvPi{f4#Et;as0_P)jMZ|>p=c6fCU$q>p9FY#7Og)AK&D_$XFRH#Z{-MJie+6^ z(`IUp#e0omjU_J)|YP&%Tv~3l}hSEV6a!mUoz}jq$(vcl7^{ z?hPA0!#^B`|KK^$iu{M%I6VVC>%Z|PzR42wbbmP-e_0j(yXg+}|9GzaZ-WKd|8r2> zfA#(EZ)g8KEXcyZ_>VyOe@-ydse85he4dAHar-ted)H}Gf%1l24t>46 zW0XpGG{Jw%&O)J3e243z>}mD*dS8j0+U#-nv__rS*gb`u0S)M7gW4?if&n+SSO5kEpzSF%G% zQ2FpS@TC#@qlq`0Cm3^up6zAdd6&2a)Mi1ZOU^s_1s1Qeuj^?qwZ4;vpJ8*lF_ zDsxO^x)9v_{o&8?5KLVFX?}Ob4&cM?_@&gm1Kp3)Oo&l%eV)WE4UCgAZ0P3?A$~O#$Qme3QBDHEv~3yM(Hr${)1vK*iiZaZoPr5 z*Q?=&C1$C1RSOIaY(=h!djQ-1;57&~$j{X>qw{wXaK~P-dEf*JP-aiKo3RwiCaE?J za96ENsRE2mSX?CMe6o)<^jgU#ZQzO&PM=PgYsvo{-8TbZA_%IFOi`^#UZc81yls{A zf2A(Wg*XphwlYozP$%TH`wfXc@ndA9bhmpHF&76L zGCX-74jV-Y?VmD6#sU(CE#%HR)nfg_zx+<$oZM%U=TXI(-%9@k%j0|P1*yUWZI_hB zCNWB=tOAzLForMQ#+`PSogC(QBG=6rNH4eB_2nY1a{^j6RXVu<`#KfFVN36*KzTJRo0kh| z;15wjMonLupalN`{2GJp4TF%BDa7r&C5_wWoB(l!p)9|xe;{xu6IU1}cChz!d_q2yFF{nRv5X>E6V?;}cs z-li+Jr2g8N8b{Q)P(^fCtm@@37VfHTaR2o7$p$XWeN34^k>m3sz1Vcv2g&oQD7amv z6i?<9Zp-$y5?m=c?j462D*3#?Valu9qg&N~+mUewr*46MhKDWFi8T>g9_{iLLS?G4 zT-!t~=OXb4HVLRQ5m&3Tk*4^oWDa^_45{L~cG#jFg=nvGG_wDF^C zSAi{80^)UdV*xC)up{Tnr*+DL?gB%8Px;`UiTMNBm^@ASRPh#Mf*|K|Vo|lbOSfv~ zBvJf>{+#anQ%+O zN8qu7@-;{BXL7EtaZ%??<%3xMmTUQ#O2_3Ofz5sRR~Q8Hk;3i`!@v|m5K#0~S` za^#Y8kZGjC5&_s|3Z|>+e&^5CCf3_qv`n>26dM%)0f1nD8t#t!@jY~~xZ_x*YQvHV zM9G$(pwN+PJB3CqFA#v{a=&($HUuw9T(V$p5c4ZN&WoTy<%D}TAWh;RJ10XC3kYD` zYf1M7pHpdwkmL?=2A~B7v3psEQ^`%%KkpXHmn)!ZS8S7Mm2xW`FY+6QfDu8!CL$Jq zC51A2zw(c%gV$@w)@82RyC@`cfdZ}P&R9|O8O-*R{mF-9aV~o&C*-Y}zug<9=_%Q8 z5%rBH@A0}r67IWXry?zyBdYu9u4f!^kS=I68}80TXh!sk9N#irZrR8!=h)eyUFE#L z^^uW*BE`**zhoK;<0Af3 zs?9S-3}lXHsOK2KcNdafr953HvM$RTf)HO}XK;+dTR1Qy=t zw_(}akSN7IdjHD}9PM~iFE_6?69&+-EV>S8S0%tu6c&^J%zH3}YmUfLdQ^}5aiIpO?3*-#?`1EBK6Y_&Aon=YP$5OO3e}}&#S2qx#GDqY20IfR#DZyd&Lxy< z_pzHF=uWWW7^v==H$T*zHpK!4c;!{_Fcy8>Q3?zIjK)Qt{tWV|-^9MDtP<{s#y=p($k;Wf~QsWV$b zKAH;Yy!ryr>WnN&HO3x!`-IcNKyFq#GKhOR!>eP6sC9FgS*wX6K)q5W$$oF7+<&-5 za{?>iaz}G~O3>;Or^?ad0%D8cOu9K88DOl!$fIB_k|L#d`(6e$X^2#OTy1I>XcO?! zUnVnd4lAd`9@qoxZVo#VJ^AMkjtkgfq$y<^?PNlGBf%CeG$Lk|MRomObGOjz*;?zM zlJP0XEjcPSFL`r;ym1Tn9xt(!u{uzYof%jWW+HV@o(#45zaGvYrQS9|GsvL6{E7ER zh*{Wj%?t#F&z7igWoZ*<2v~`@Y{HrU^et8R>eG8Tyg-|2kg2vqWowDe)sT%GXnjYN z8v${mg&UoAbNI0_7Kj`jIt=9IQ1+c9u)!P7YX1_$dh4ec{B7ut$su3Y9HBisck&Rc zF*~cEBC;d&$z5q2ZM*7dO`9sSm1O^== z9+mt{_2VV@X|Er|XG2R_5hmG+g^Ti$XXe%*>6oz&MJlEBj#)DBmmzz&6i zAe#08ydp**=L~60%iNU)a}}_^ zJKCqn?!m#eKzw02RMhQH1%WGBHAG~OlsZzy1JN%@B-7h-fE3DyTBM?(38E|nlp$3{ zrj~WxGbNP|vP|2Ge#Y7q-lU>E&WoFAWr0$`lE%ine@ZDc_XdwcM(Ft1c>+FLhY!Z@ zUM6O6t1dvNtz4@{PXJ2-@=F^cj1td>DD;6jYlc}UUkQ)__hD_r^%l=5*LhR>Hu zt)O(kaz97+eB@vD=Oa!g94bolGmu&YBMgYJ{ZnkKI!SG)GL3nED=LB6-WI;1jB;}s zVTxh6zyqylNG94CXBuVy#wSIFMli6S>gBSH7K&+j%>&JBOKJK1!OFq{w?GYKs7NXJ zkmNX6tTF@1wk#n~TK9Xgo1ShtO1Q2{WIPis;qmEETtz!E;W&-zHmknW4R|Msmj®g+9mh)rv_m)kof*tD&92DfoEOLWp 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

{=y=+*2-wO*CWLR#r(y;)aa=Ue3gu&4?rW4 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 literal 0 HcmV?d00001 diff --git a/doc/lab1.pdf b/doc/lab1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bd2155cd99ef73d8b8332ff657801d287151bde0 GIT binary patch literal 933686 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

uH~<1 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 z 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

(8%WebIpL=>4Ndi5Z3Cl~rvBz@PicS>6 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

Iqp+V*uq^R7z%D;>B(G)zrcF}QX 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

jKr=JP8J59{ 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�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 ARR910 literal 0 HcmV?d00001 diff --git a/doc/lab2.pdf b/doc/lab2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4ab616d4d1e65b56061644748e19044b6b83f50a GIT binary patch literal 708103 zcmd?RbzIiV);@gGDGk!xlnBz@Dc#-O-69TPp<14PPd@XrH8CLklw-pJ|!A}=q4il>7q zgNUJvp^d#cgMy*CsWXrn{8@=X#?a3Ev6ls%h#WPLLD|L0)!0SF$<$QI-rfbs#deoO z*3ijX($35tT+HpQsR#hz45qnoV7a$WGgS4fIvo?_B zwmcxqU9mtGuDd7JTQQY@tgQDJ_PYxk3 zyJ)v%{F7*48#tMQMY}hGlBu)3tCO**Ggw6pc_S-RV;At9q%GJ^Y`}XX+&wTc0>Qtx zX1jZ2VFiMJ!H@j>45A(`;>s?DE~em&;>sLAjvw#E?CoyL5@P~#-enYH1~T2L!yv{2 zWV+LjL5vm1^y4cVkomp>F?JyHea&JVK<2yB7{oY%EO!Ef>r-&DH&!-v(PjX*I)jR- zhs(W4!uJ=E`-|kQB^YE)O)L$C?LD-?0x$yExY+3#S%I9a%;1YI5Zpv|E?`SI1Mf6p z5O=b7bpQ)|=P-8<49W~DPKI{Q4!0IF_GA!IW_W7qW@&7yBrXgtUB%GI8Jy(ab?&X| z3}pPh0d5Qa*#KgeHZG=43}QB5OFcC;wl^_lkTJD0cd-DnGPB<0a&~btHMD(z=<#-( zch+vTF75#K3e~F$N@JZA)gGzSKc^6%Lc-r9FOZiN_W8)xJ}%GR{+?sh>cJ_Y677y~ zbm4Spm(j4YlP32mPvqPj=)I>$9jNngR)n0GB;|&Y^;F=>B4wymNB{ctdz^i)oDj?c60Q1nv-boJS~1(0P~AP`Pl{k+cS=DfY@FOMQ? z$-NU` zrkAK6RD4h8PbW9zj=vm`fF_z+{Z7Bn%AX$GT<@)1lce(twDLCd`}yFZ zi#{@0G3N!=z!L2EbLky9n2&;XL0w%SzkYi$w6!vvZ|Wbb_2jV+Z-lZ&J7%*vEs>T6 z$n)WXpdRDW63XPo?vf<X>*L}YPDK%Jd_IC&dN{cFTZUrID4hpx)g#41?h>&o1vG;CB8y-^K?+ZU zazKaJv(6#T4rvm5L-M+q4mC_(f`I5;4bt+=a`etmd5cXjp>zrRtt~CS*GeZ=Hp0kh z1UOwnx#k0%G&lvd8dzVpV2?2}axAF4jjL^4k&7tO4?ZAa_<@jxL!kKSv#lY3QB%9@ zf()~mPR%F=0F{;PqQHV? z%zGaG1qlAfoKr}QkKJrkJIWtC&U|+QJtpv7E=c zFbOH}=g3-HYV}A#YqjHM38pb4C5G?N&l8v5qYaiCz1QZG9G_b5W!40mKQg>C>)uT*vYd?AHx%mYScC zDd0gXy&*W~f<&as!D7LB{)KFZ7RkOS!QsgTO&utgNRe3T{hBaOGOuRlr`^hIYUy zTcw9tlukH{v1UiRT`36Zx7nCRS~dN26Ro3aWUUM& zf%%p=nRrx01x*up;o{MBgqoH8@oP>en}=vA70%#_aXuH+def=;VGvatHY=bvqMkB}ef&5z zUD?^3?JgcM$K+WXwOuDPunFj>+J8%ofN3m=Tl3C(ep!)B2Rd325*}I+L*k1W6f*8K zVm+Xej<39~A=SnCLm;zuq(^sU?ld|62PTPv+RVWz4;3Y8X`k8Mk>S@eK}54yApbr$ zj8Hm-Farg3=8ZSUWGLK96%`QKQ{2 z6c1+VCssaiObucmi+M~kAntm4L7?PJp%J2;J$m@&)F5+Z|yDyCZ ztG|?Vb*${S)1xDT%+4^i3m9tJ>?qxyG0w|ZGWIVu6$gqofP<8$LSj$KQyDmEtixgk zkUy}#d5f2h`$=UNVIJ1wL5>m&j6i|f<6!!aCU~-nalpMZ6GH0fq{4RR*fC}0oR9&V z0^tlO#1!345m?)-_WX!?!e%YonP2; z_<9=YtqS`J`=%N75gWFkl8%h424TkIRB~Qi;y^WDbb72QOdc`jc(PrcPN8TN2Uu_2 zzL;vm@lhHm1bU6V0EG3W_0w7a#}EVPOkHuel-3RL8E^k0PV{!TGW(jd2;+7?QL!Y7 zs2O|&b)u{@h_(BxyNHda(mKSRx5s*r6>^##Y?0Cj!>;T&RU1!bpLja_C&VSi*6l+N zf?h+QglaU&dbN{%sTX=AHD;ACNznaNs+fvq$Z%w)___J@ywxD0^|hiRkx8V|D=z|* z#}^`B55l7n9cP7z@JK00VoKFLvSxZx3guii^Nhv$gY`!WrqediTy*4EV-e8R_Mq&- zJF{pVFrwt!*7i~xGA;Bzd|?hu#5R%D)L+K(!v6pdcomqK6FAeZ`lTr#5ySf_k21m< z`E%!wecc*yfpGB{=>gqR0=}2m&J7nsBs) zA%CtsyjmKCLr_VQG%qK|h5`_-K_iZx@fp!!nbxIOB_n_US`|7Z)F)GSs#BL#uDI?E z;z56z3MP0`TE3V$R$aqaO8_$Eq1%-+ zx^v0Cc<0IpZMKB%t38xw+NRFEA0Z>9C{2=bVEG1*sExAxVs`~Lsb67_)s)1-Db=fO zQ+JFv`8_y_Q4+LzFV;3Abi?>{<%1310`&wXHUcj*izA z;CDQzbc$T`9J${0np8(Vw;V``x2cPKc2*ih@Q!Hmh;4-~t1c%%A?-pl?o{SP3fC0P zbr6nfW?0|`aYg-01Q}nP7L?}}JxQWl!9|Q<&}Rgwn*&u{RcA`Eud#{)MB(Gw&`D1O zKPK8?+2iGs`nXGAW?=AAh0PDV)w33<t~6f0Spg>X9({!TI=SBJB5dFBLa{xaNQNUod+GTRHFq;F925h z*Kd#In`p1Et`jE7kj`OX@T_vEs~X9$Qi6?yhu-HF$SB&;mKVd@JZ6;^mNi!~+>amB zrwS$5dWBY^%=Y|}MiisKEtFXlF+@a18|}Iy$yuX0-`I7Puh2}#hf{Qw&cBK?lNCh@ zNc$*Plg5cBl0rSLX3w2NzX0VUqtJZW(GH3?GFlT#lw&j-v#Oo4$HYq(8z%EPZ?7BQ zaE{g{_F8uXit95iv%;7Q&tUjmi-od~o())_hG8O2>$JpKm#2w0;FUI>nGN3Cu*$U- zmiNuLtLJU(-i{MBiiY?luufx#kL_Kd6WP5hiat=qv_j`x!Dw358AsW9e!!$&BcX>+n( z9=llE-+VoKGUNuj*gF|q^ghQ}ZzAcNor5qX<_GV{Q&r zgT!0DR^B6XCdubhBL|$#3X1QB^E`A6KPZcHzfoB>EKGU|(hqbxn^ctTU#VqQuAW9< zQ@b$ArGX=ncvsntuPk#lByhB*&foQV{UM60z+ylh!pXUGQoYnHbNpe#S2ZrQwUWT# z78-l{)j7TpbYG!rV21qSn;HSw^QY5?uA;PY*Yu?@sY!;Yc_m|XDc__qrzveptLF)6 z%&VdX!iFmj3_Q9F)`w{ELXsZX2_>(XYK+8KaveUPua zeHLGBR0`Ep#|M?zcv*PF8e5z6o`c(0bORyET01{U*FUYS*t~63uU?DD*|Az9N_(HO z1G&&kd&NK!gg-t=jGnPjsbI2BU*>P;B*%d5I6S#v0HtZKCFr%_%iz?jZI>CgDqAaA zoy=@pnJTbakfO}WfR-4)u>&3PH|k1^VM7pXJYL!`EN1{z;n;XvuP zy~HP2{g~T%COe!HQHb2SoKVrhF;c^rI%cqKSs@SeWXayBw(H>+Z1`72^tjDaq$s5A;!>jElMg-)D0%JH9M0hvM6R4g>ZM6AD`N zE@*_O*o)HR-Am}+>7AHE93EAUzVa5{2>ymJVjiZc98?b(J8M?jdz?pvPE+ADM01@* zTn8#g@FowY-#lATr*CD`o-KYb3f;|6D0|r#>@(TlSYvN}-kgY+mfGcHyArSEoaQA1 zp$_Tdwh_<#0kQu``bd9u8tud3^;(*?f~PZx&LvBq1~YI5lHQ{e&z2r-p^7-1Ji@4h zXxabW1YEw9U!TYXOhq`hjwK(qg=d?oP~3^v3j&r(e>f zRBIT{SxL7vve8l8a_}1mqGr_Pb-e4G*APVO&z~)QS+AeMBA#*6e;JShx3BDAb)eq@ zC~n#Mbhzs4RwVnlGjM))nb^TdpfM$M8q*`KejdTz8OaTD2;I|&s%cB_s#38ZN7!)m*?YeBA(DzZNY~;H6x%p*UjgdN5r!Fratg^RpzE}8U*l8 zg0Z>dx_*)qKWVR9hUO==amSeaV2>D-U5#9BDYrYGh(X=b1k7+Sb1+DlTAEw909iP} zw2O_sld^-MvFR^1g+bWT#aY4BNyOgP!QSqcp5kPX1hX?v4)(Wv3DC^Y#@UoXS=P|m z8hA&0G5l*fg_-S^g83=0$J@Pj2nYZG5C8;~*+~e3Z}nl3VF3_-tJQ7U|3_MhI~jU{ zSwj~W(|h)b5zILKM_T@C&6qg;)Xdlb7be>P>y-`w01f#sG`i)_{?O6Xj;t7TQd9Ozw>%Ng35g84lLVMAxryA(pgB2Ps{pC}lD z8_m+ro}8Ic27I}52T?mPK2klhD6PX1J~BPU3$fC77KvcfPGt63iD{ z8VlK(+n53w8HAjTZ+(&j{Nc91+j}}@PF66j?C`^{Z$AmSncurRD=Qng+L;25$*o| z_CXOH01du?Z%M}$F@D|OrUO8PWB>r~BSEtO5db_aEF3HhJRBSx0s=fDGS&lRBqU@! z3`|rkB772JB78zZAO$@Ykc^g`knk}FH7z3(D=RAr6&D{TGcP?0EAwq35C{ke$VkYz z4<6t$lM<3L|F?fYZ2)w5z#{+^6a*On5*-2x9RlK8kTMCuVk#QKkvU>9Kab9YCl`L-j;%Cu zNWo&{6o7z;^AHygpYjpaV`>^!Hg*n9E^cst6cd+_lu}kvRa4i{)G{_PH8Z!cv~qTF zb#wRd^a>0L4hennGA!mzY+QUoVp4KecFw!py!?W~ipr|$n%cVhhK|k;T_3x9dizGl zK95gKPEF5zSzcLPTi@8++Wz|O==l4|>Dl?ktzHlSsC%{k((E7fLI>*w2@MSe4R@;- z1f)Co9~3$?3@H;VhL9qhp(7?4^K*DC;poiw?Fi&7N{84+P9umo6s%t;zuu~Lr`dm| zSit{Cv%eJkrPmAq8SI#l=uqeY0l;bfM#KV6yxL6oBHjgn=gk<=PC|!vmSP6oc`$ zozwCxdI`8yR~9VQFmHCQA7Lk0QGXk8Kp8m*fa4EZd8MY8`DK9rLi>Rj>Ei(Q*W+YS z!GaL*eXTAq6zoh92>b0tNj4)oy_+K=GxH+P>?+%4qbs-#0#M+|-$SE`Vh0 z^a<}PA5rB?;!p}xzh$Sd@gy86UVT3BHY@ggLS3fvM5!fR11-^LnmL_1O_Q&wWrGTF zxSU9+RsooAF#{T9E$L66NV?Z&Fz72!u$~*o&w4d~R*a1fQ4h$2cJ5euKIgjRuabs0 zY8qfxAKNipxEF}n*xox|@Bf|F0Rhmn5(PnN&iPWEhRnj29-(*sz4@M9D@(2jv*K#^<0HPonYxGmS@E zpZ8XBQMK$+zZj!Gct&d~hWZ9_Pj|}pLlms!90VtvRWRjq+b-;!)cBAXH!UK3vX5ae zGF8POpZmkT!&1~pOb1Q>ZP0YLs`tm(a~BN!3yv_Shg8rKj;ZCxD9}{r0as7%3z{B-#F~RMpzhVM5@UZ_Y zCSYXb{C6>djH%&mtaLm4|A-I3fx@j(f5ZozKsxXz@QN%WBM@B6KSl?4E2@7e{!c5x zf3^@fUT6F-nnltC9Q9bbc+yGyP}jr|9FKrSat2EYHmeeYioL3xztF6HEBsvy`iK2~pBKnGnV33( zTj%l5dcfNUluXSn!7~oe$3oyu0j9V630xffgzu{P|D_B5x8@YAzr0b! z($>@&Jd=a<8;$Zy^Ebe^xk2e30=M#S?;eP=dTui?}gPDo_ zH_%|`V)+Fctc*;*K!cf${XYT?R_32`ia#&|oEto+06RbPEoy+P`E#$x-R6sba?D?I zzQ2Wt*5coh`)7an8;D@O-}v!6MBIbG?-svH^aCQ8?_mCSh`4+6Q^0?Oh&w_4?;wKt z$L^FrA%giH=WZe59xeV5{&$G|UqQrOH9r--n`hr5+%KrQ8wh@%$lfXcUpUSbC{ta4iaQucAV4Cf(5QCYG?T^Oe_y^kIXNd6& zEtr3gFu=KgM+**)e?W_WqLuDO;h#P5w(WikDE>z8UoG?RP~*(*dm<#<+Cn79A z#)!X#3@{sb$Nt?im0$+%ZlmO{X!kDbf7C|zBK`YT;bLU_J(ytpEtue7=KKvFz>|qz z@W9T_c4xm)cApyBw@tfNF*^1^EQA zJ6rvJ@N?!C1My~10E-tw)eZaUN;a)_j|di^Z7=RhbaVZ!s z1c2>xt9Jo!q5W$FtCfW0AO*EEvYBW8P01G6QC)oc*_Dt0> zp82Bg6qaBM0sCt=C_qnJ(haH?JN(Rw^a;&+@gq*@%5u! z5-C;SI%GMz>M?rZm+PM>9a?X($-4##@J>OxPOo;jGNn||EYUf>DW_7ABu3R6bVAXS z$D^;9bD+b!qV(r;bB=HRULifbldsw}WLv&r7gN)EQ~5TwZO&oXy8h5S1vv}@Vkul_ zt5sDa+q-C$p3HO3xeyedPvTeS>Z$%(WKvshTK#(}lBeptb?0>biTcIBGeCFp@0=@(&8@|dQciK#7jtUt~z#(GRN^8-WX{-6S9O#u+Jy4`;i_F&v-B@-) zkn0tnH7hqRNOj{oXr*5KT0n9}`@3JLCDBwa#2%!7E_$KX7EiRIski#morb1DDgovm(!A_@uM9H;r}tWXd=Ie-Hi=ElE-;@e5f^K_LJT z@77sXiC2IZI~M4G9_U+F!!xl#&#wmp^^k$Ecc0lrgvvjJQ^NzS0&iW=+kfKKBS1dN zt$TiB`eYFxr-%rMg}citzc{83pTT?UioN;$Zoo-p$%J`;SiTPkZ=g&^tc`0KynO^*0y>k3j|2sNSiWq=%CukT5+D==M2SBsMuk zTc@iKDkGk76?)3Sx>rJFJ|pemNr47%WyF7HepHZNeJwwL(yX`=aqJ5`bQD!?lrQe zo-`!tS(@IX2(~Cx0P~1T5l-aJkiGg&zeWr%3`A-GAT6L%pT6{=XhH1A?6w6@w8v=0 zD>A?mKj#&tQQ?Ijo7d-9bD3f~c%_`!3*k1{qySyAV_Cp2a7J$z%W^ z{82u9mcI3+e(W`o9qF00vp*T24X%>=MTY)LJueEX7HIQ^3;}}BFAH9c;_5jSZGxDe%JCGiv@C0k+yS+Y z*@0xx5ScUBwES^4WVy#i^{!t(E(tw()o4C7DZedneS!mFJd&YOOUQfREZ6EBzQL|e zssnA43%d*z^sRr4bCP{!UIKsg@HJqS#JuS~ zJ2*o&rQMnExC?NV-N!fhx@B&tf~2?$ap}~BqgIdLsy}C+)R?t=z8L!M$K`{4^A3V> zug*DKoPF}0KG99~XZhLV$oG&n(@;?)SiC@_7oLN_c2I+{W zY5hevHcFIB&uXy=0^GpsuzK;;4fq>LAgaw#t~4LssjnBs>MadjEGavclb{u`IOK|s z!_v9L_X1^`GOkoNMe$na{G2@3mv&c^X}o&&wFj#>sJ5kj)n_S>0D_Oe@+RT%oWFGt zkaH90y~DT9VY2`7l_p1RKW>=B8!`h+*KBMF|i zkQr2N5YdUFbq%P70b|?<)V#I>7ETwe@+{~c4Eqmg9eCd;gXFalSue<*tGqZdd-o+| zoc3v-T@CjPHH)Mul$MtP?Qm=>CuY~&hTqrolySdJ7(@iHl4dh$9NDK0x@E8WrTB_4%wN#;3;Sy^2$@|orSGZA0lUlSV8ApQQH?)nwPWU?g&J*0&tx; z@yJ3PSS_V#dLZ`ip81J%j-Q8Wr*`AMn>Em1=c!H~Mr$>B5lI>xF|iDxs?%tM^bNK9 z2WxpRHAtOl+0-CIsxoOWqopJzOg~v3K(0H+B-3w>pr|<)D{mv<9x|4N?YLl=a}Y7) z>5UI!H8f$>RN}Dmtv@skM}3?ykU8om5Mo3il8_#_*}60r{ z8CJ_@WA;tS(@&GGXs&ra7Hh4`zoi?HFPNQR-+85*oWJiGJ|%@gELgH`*%tg~qLt4t zN4)EX)?#l)^&OA(5BIVxI|d)UdbO^$J$#ira=iuZwc_{gk36!Bt`;{b!xtuyd$4-H z=P`T3%?e8C@#`A#Yp)H@Mc_$cWX3N^c==-Fsw`#+`(XS{HFG*4@ADg^7F|}^sp+8T z5!`F>BY}Z~va{^*NOCn}F%Zlmee$DSn~olfg;egkt~F`KGNf(^y#~* z-xc@bwjtr{OOK{H)E4xP7o{=X*ukbdkRzH~UZ%ul^}^oK?Dz`>yzWqd(7S0+1WDWT z*@z#*ea%fgt`6Zx32oq>KiZ9pLaL_MW zi;Bf}K=rd5eH|>FG4I?E5g5BKql_L2LC zJw5XirY9Pj&bvvUPbb#WPyr2bHR~>y$?UkoQtfS(Q*CC`C($`l^vft-_@Sal0=Aua zz$k!t$!`Zn0ktdlXAUiAW!J?M2ZrutRomP7{Mfj%khx5^If z9cB!6Ryv#@*H~YY*Dd*7EyanN{T=b*g)nUM3g?dkDie|VY! zl9ZZz38-D8|3uuvZ^nixP+r+oy1AcsSCjdRnEcE6@QclYX(Q7k`Xb$H(C&^1XmN;R z!;i)O=0Y^>W3ls&A2>>0spf1=<-INgExhaL?<5unF+7vn)%!d~kO@L-f4JdSlO8s6 z&eX)TllvW1p0U9KLcJhKNQ$5`zu6iH%Gfb;kFWIeOQ!<2957XV(@z!7u$qq4TyC(o z6jXtja9Oeo8M}21qJT`32GxdpEY#~Byaug*2F2W4-kB(k#~xG@;LC7BKnoEeZ`-^( zL)ocUy>s$)vF5w}>^O!%^*bG41?r~2jzBzFb7~%@8=r%wifG?T_@{oOx#I_-iUO|c zJ++K*t+`%r`({f)*6b60t?~yCOuF^U9AGy_K_4qABeKLUoX^fa+qw7F2@mO(ugXEp ze2|WLo2YWYs-MI!a1v>VY7V6-bN#Dhi!tby&rt%Z;+4soct9d^vfs1L{fGw^3G;I| zc3hqMhKzng0kiZ5QTbR5rMskW#Zq(WQ>97*C+uq=L?Y#Vak4=H_77$6yWeC14>h`% zpK4`Qu>FTO+c!(Iy&f!v8Na1Kxa$9u>uXI8E$8ii6RM%SSLeRY%&{_%6Yv`#dM^XgHx67Xj=qyZ0#B;2|;di6stTh-#KS2XMxGxH=%QOr!=6-4Rfv{&V{z>Ho7$%--Et*r&K~COG=glyTY(dq`eq8_J~_*S>^e)f}xVS6>a zMYv{k2JW>0i(+Jz+LZ_xQod`>^TUCL_Gu5|bh~|eKDp?yJv(qo_Kj?Z#BQ1yQvvm? zHcKAH$_~ATiH?J_NGu4%iwn!@K|Skxmulsye&%-v> zqwIuryfx!eWxUYSN!|Y~>u2RPJ{hQrAn$pOYXOck$L~8?tHYqwPS9l0S5;xWRaL20 z|EQQ#^J{&m(sqS|#fXcpr%u---Ong!hc8X`;Qh5N(4CIyWO{eB*Lv$p#7NP(~)eZlHcP)00H&Q{sQ@ zf72b!KGWF9JV=s{O5RndFhLY_BhJWm<;<7S@D&Jns7+pC+dY}sY2KM>_0^BwKJ(FF;R7o;Vw=+MWe4dW!#}<7Hp{>2g!8DOc(dINj2 z`$1b_X=qOG2-cE#@XgS_e^@JPDx~l8GCyWE6&oR(k8}PT`EZ|cP1LSw8-q8*b+2ld zpO8cZ0uwox@MssLf;hEihF;bIBt2CB==2rpupU*vCbC|43!mkhllI-=%FzK_cuV z^8&B8Ac>v^bjY+oLw+nG=6|okBwpJ}BK+mu+9@{?j z-px(AM!m)$CWJ+wTtnCEuF*H_QP9(t9nU3a#!*2j?#vN=E3On_5de=xcFr z<1=j8PeLuMxmh`ZE9Z>_5pVm}uC%seEU}t+HWiYyGkw*s?#X;W>VA1vMtH5BnY{hx ztjtGPdCslTe&99S*Tqhr@{4D4?Bo15jjtc5hk-`FU>an8u?wQo;Y{(}X4k3+lzN3X zGWowP zb1~_?`@W&pbvc9J{PxSkD{`c*xvv0(o<*-#fApTMG0{*Gf55NaL*>|T;rBv7@Wn4D zf5WaT^9|t8@vB2gCenL7gM54Rt7xTn;mHDRl)sfD)Nqjoens@F*A13!W!0j#+5YxM zerxoFKpWJrH*($$iQ{-nq@5cRuJsQqW10)}ir~6YrzwRz7B7~_DihFK0E)=zvkA3A zpl-`}9=5AG&oHz?bKTrMYO}5?WISZRqJa}p1i}quW|tX3@gV(UVdyIFC(8UuK)n?3*0XzDwgP6 zjb%X^0RLyNP_if;=1OD(L!<(Yml{b|AOJxI%P?cY>T-@5rUT-&v6>S-r&am@K+qCR z$(Z0279xy`J9QlUYb;h0Kn76xWqA#{40O9i1s%T479%#AGoGXIk&atKg%jiiJU_sn z_2Qj{gUKBB(UGmP!xY9}d&_?;VU2zz325?p|F=6h@6M(9W4-S75S#y-lVZRpxBS>L zakt+1e{>?ufA1U^Av^G2q1>LrbGOM4yj}7?JQ#-Mep|@@&B-u-{DsY3$v-Xruh%VE zx!5@WT(=zOn~hij9}V;1SYUGmyNyN<#a=KIpk4E@Qo?^)mf<859odS9k1bF}f3$#N zs&&y>qK}MHGgi?IE*z+Ar42qB#Oo(|#*W0t30qh}Ur%J6uS6zU5h#2?mw7`&1M4drdw$nn?Eoq5 zjwQkHpxN$P`$*$jqy5Fj7Ov$=i2>_FiH}+<_=cP9Olgn0S-xl?zarzF{uEgt))3F}5jV>Ojl{g* zYTtcP&-eO#s}rexwz-Md=WJ~yJ>~TDpvC)o?79(jeew0CJ8wf@K%n{RV$Jy}RwCN# zPaXgZHr9_$%N~85YNsG3P*^w!ln6%-|1E+1T-sJUPYI^U4-JUzyxt?%=dgcFo`Bkh zs`G8|FmOSV`Xti0O}VRLVjKAo(Fve}L`D^QAP1LgBW@90jtTD=E3?ma5D~W5NJbr& z!ZeIPz3~iY!Q|)}j8sgt1ee|mGE~H;Pj+8~%28vzqa7>M{Pgfc$bO&J$2x@poC7Q! z;q14$=CsRQ0wPm~r4*T}@Iz+?IgDzI7K}DQhu@A4LY5qQ-<|5~>9u%komg3TBM&}6 zS^DG9}cp0x2;V|3K70n5cd3&@5VNaKj_c z3v%N;qH>^{XR7Jy`1_jOldoO~#g85ApMIKq@$3oZ;z8Bs2@l%n%VutVyLl)U9;&ZJ zSuQ5MuWg=)>CkRVp2Fw%VIF0oN(|QL|xerf5?YaTzonMBJ=p*-W%jESQ!Gw8CzhpEF zk8^4(6m}*J*AT@kswkoDfsm#@Y$DEy&uflG!D;fCkqY@aBv!OOMa7mEGLX23=1sg zd6`Q1@SXWHC|No+l)>Hi@zvr7(y5yY@ihrd@$*y_0u{|jKuS~8H-T7lsnn9~`Tz)n z87Ame(QkpI@Ufw283%}Gq%7YGd_Oxav|kWg%g3r03D304D^jMAP>1syiuvftyc2#n zey}nJAXzISdN${D3Pf4nWO)3N-!AwWyy2@B$of-oF8S;%uf{EvyL3SVABN|Iuo6jwq!3wMjGukNUW!O}vTRn_z`7!ud2jcb%ZEbptK% z5gYBuTZowJ7>t_vCYFOx)FtoStV&H1{6xRj}{Pcha*P+;g=Tz1VN6kF44t~%>Y*-mL)fyJxWW=z%_jNo^U7P$20 zj-H5`X`O&PHf-7n?N0SbqjH?1HIg_FH~AIZ#hnCk=0e68$6`}5a&upbv3bY$=UufB zt=BkaR^{h15B26h36F?oI<&QDF-V$@7mLQ_aqDa`KMEGb@15~CqH=|If?KmTSY!$c zdCt{{YAmoK6+4hnFL?AiDE>61b-sSU-reoUJ!TI?8|K&NXviRr~ zqKz|R|K_*NM<`N=h(6;z!b32WZG^cV^rLH%ex$qpU5_8IzD|Ah~?a`r)Q{n=HjAi@fJDy z1Trr=f$gZkIYkx)9(v>$*IkQ8f+0nmJl~qRPB7t^^_b`MK$*|Qww$qgVPf%}3vFE^ zBXU`YW%ICkjL4yh6znDV-&}-?CuERfeU?s3@4}(=##utAb!1JZ@HC_DpjzC${zirp zn~Y|&A~(gjVUBABZ%Oi1zZhIm)$(MYgN#|SWP;OqH94?VuQVcN&~EHcLD z+XP%S5|)OrLFQS1@6gPmzx5+Ovpq!HSiW?!ye zFnSxU(0UtRbSy3_K56;nDx7$JXOc=V?fSUbo8fn_DQ)IzCcS=eWFx8epteFx%hxV zG6U@j<0_QsFVk0hwXF<~Xmksmvb)|@nNPnM-D}?)%FIB`@Ft1pC3M+x=MT#S_>_xE ze@#yzf;Ctfy^-r@IQ0DxTXnrDhY+gRrTKk0x7{}kBaS3*oGM0Z6MvoEmx z?qq3K_)-;@^&0bTQv5q!v!wDc`}&S`gBt#-~czi zJk%wJ|7nf$XXDRQBT}^v8UYuVv0(;l0F;HY$%B* zKrGevkv$Ecakn8xweSi(5@;Q0g!Eg)g?kJ~ycPEPhGQ zOEAD8L)k;a1&2GLWYWsAl#iaqtHJA71`EpLGDuV`eGyJUh*oiUG7-#A9BxeO$}RS! zg%Tm5NyRW|4Q3g%y^^;|xU)9_5hJFpve^yOq}oe7i~O~iLtMh*gct)_!U?i~0b(wn zAYeix2k)_0H#H<%J&CX!F-X8YTz&Z?=SP;gmSawAouPq`sXG$S5vYB~9VtS9p#$tC z^=8z`biR7jgrwqFvSB%sPXsNt8^t)wvl&|s{pPlh%c!@yRELrFxQ5_qZw{d^BM@hn z!cOQVqe@r`3Y_q&DzLL#b;{nG4$@z9Z?Xwv_FWze?y`OCPs#@h6mvVr&}guzienN% z1lg&f+A`O+$fGm_j4DBxSC09s7fMhUVqqw$Pkpy>_a~KvuIv9t+&h5D)@)m%<=W+4_O9Ax z+qP}nwr$(yF59+k+qU)U|4*O3=iR>Dulx0Vuk*|NGV;q90Lz=`e(_gN^*aGBayg{2eoX+TV%uklDpcUXW~& zI+_<9lfRv2F^*~W`|nLjW%$DB=gX-;5J zO*xf9tzJ{Uqyjmi?xorBxk@LzIGj*3j^5ldIc~l`u5!2!R4I1|O+ppqfOwj&a_IwQ zb{Txh@%SW4c)7GjK*^a4czJ18H zR2u7>Z+-w&xvK^o=Iv{aoZM~@8PbdpM%+2g3fdi=fU=CUtgn6)aoUe?iZuvXV?%Wj<2kORZ?At0kIA)WaVI_PE!v_uphD5o@l`>VxMe!of zl)??An|b#V)%E-EI4*Hj3j*iArUvR@vlOg<9(4k(LLopE?^- zvYLif^;@KA({7oV3M=H4LIjFyq-;vE>z;r%>R&))vqR0Jr!1QBamAqqPDd0@5(v@V#f(=D zB|93?%%7Y#{GKc&ygp;%)>7V66VE6*XU&|2WI6q(9+>s7nAhf*y}?GzQQ-v0T}B5W zt&5k@{kP`6us3pN5g5_8y2l(pWlTfah)c5gBNUnw0K+1EOJZ|qfmLE>R2yB@Yy6Y? zPWqUuU+ngD$RDN@3gOx{4DTA{zB1w!`VK#CD)f=2cI$^9qINpu0%KOH%=ZVb!)s$*O5gJ=0{X7Jj(1UV zKbhkV)hptpDPejMZk1BZ2B;DzWm^yKHy77TIH%nb3ESM>%I!}UQa7_@HP#8h=VI2a zUr%~xJU)5)^*nb=9g20)7=M&nTvqD&k0O2Seg-{Wn(fz_TmjcCp?gG*qrh+_Yh#Dw z*k~}k5M;=oz&}=0)ZSpED%ooL6W6Nh?mxUr38UMPY>zTJWX=_wha{a@G>_WK(covP zC*!1n?c!spTEGf3K1+@bKo(58%5{!Qi&AurwD0=Lq0k4QNf>;tH>{ohI7WH`xhf^o zoM$Z>?Hbl2@NclfJI#oQnA9!S9;hN~ zW{0S91}DPSM>{FWThT^7i)46Yib0UP1%NRr;I6W?Zl?slL3f?FfkgVt*=zVc;%aeTn(Z;h3`nFkUN_6-%1z zgB@^oPwa#fz+sY`{Z}^*ReRr5JwWHuXZOx0%ba)>Opn>bHMG6Q5GE$JR~jd%$9qD6 zf1;D~%L4%-he)&c$Vee^IVQKQ*ICAO)!}~rlICjCp~TK-#mMc?7SsCv`> zEq?S*k*2>Rum6lP{%!Yv#KT}<`ma0;*8d%urvH98lb)90--|P4LM7$n{uFeVz6|AT zA%04T!uyIw;0@c|*h>|~6fE^Y1*zxy7EJm3MpI6H6iKwP;a3Z=E!p|tt;)rByld_k->f;jkN-dr?>5Mu0G1BQjNZ70YJoM$vbUvW?a$yupR>d z;_6j;jxDXlY$8Jm7{XjVe|j3F?Y2l%MxpsH;#e7tdkaht+i5^%W<7%C5m54TR6vG zEVeE=zNt4_O9}34`r4aK{?!d#(e!FY-E^VxM_V|1(cxcb5U266r6_4n$ci|9y(4)zUUbHHmMMeFPXiVfvK!bCPx0&R2uHSbJvZTB^<5U>5T^(E1{Yc z?%wXWQKhZ?(9_xajXbqWn@Uy=p7m=HUHY=#lbsL=+yC5JSq&)5d#oH*Z6gRX7+Mrl z3aybcu}#Q7kf)SqrIwn{rdIc=HP2h%p{{G>{!=jRmqISig?PujnjooM>-U> zw6%&O1=oh!aI5bnKwSk}2x=c+{)hdFg5z*chQ2+)(tX^ zjXXajGBIiJ*cXG1MaaBL2#&)#q|2acR&I5y=PU$Ke``}l?PcWcE=WA5UPtRP;#USv zepBQKruP+vd6#!)+zjxQS8C@hmEKf9+wPf}#X2Tu^&oVFI^UNIS45!&T=kzp5|2U{ zA-Me`PZ?zb`S3Q7Z{vkFLgR|7(iUU{j!I(Yb@Y0CW8vkJ9%1@YW=# zSCiL|=wIvU@O8IHsqo>X6^dC-y?^qtB`fC50Gj8zKDV$`pF=+v2bx*kZ2U*XkJI?- zC;(i7mCw3PE?4h}bJbEvlY-g!>KwV1R>{BmKi+AUq;pkK0NR27Wj_0dE+?~Dz7_3H=b})mxsZ-6&~BgGj_X)^w^3hA9;;na z-%TGkK)r<{zp;Aw$GkrmYb!Qpd(?i>_q|z_a2JjRV^dcu5GtwnyluhuTzyhOVd3pR@M`GGP_JblN>!{N#Ms6ltr4zS1z`})$Q?koq z^Zj)v^nA#FoGfES(#A$V!PN@M;r%V5k&>DxaA8ov=zN3bAegRps~tCApZfKqYuKog zcUv=W_Ts`CX~%oON`lkFRUAfT0Q!(`*YCtdFa6)3f30kCG4mu^iPHn(YGpO!QAcg# z$>rL6ZQ2bJdUB8hGv*Zl_rA5GB}3Vf(5U(#>^R4traAnJ`uWXsBSrKjtpdtW z?V?Y}bCS&9tYvt_;B8K}ZEc0#svN}LRm@=w)4t)emrXU3DFC@|D3y;M@sB_C zx)HMGQ}FF#g34kS(g*KvSMY@lX?A2%Z@@gT1$ll zuhkN&7CSelnKQ~yd5G-UP?c7AbLyd5;MAX*rhOrg-fZQyuQo#U(HGHcc+se%F67fp zul4OK)if3)bYhrf{;~8M^^trnLn;2V-(zxkFqNnis>)dy?|FgEybVZg5UuVW+AVg6~?{?fPwd_!zU$W73 z7KmQs0C(q^Q~$maY-10{HH-?`kx}B>LhEJ&tj89&<0{ogQi1-v>z7C;aDo%cIXF(n zN4nR5c2S1pAbugEy~xN(QR%CU(EDj-LK2!w-?&40*$3FIyw?F(ji>Kl(-1(Gw-U5h8)4eDjdB|HH$y1E7!Vvm!38J7D1d5Mcfk9`HuQm@(REL`52LU7Zp z+wkRO2QQfM`V{h!>^fg?(62SQn_9S<4W`P#g+(d>i}FbYG4psFgKP7&YWAl5=0I3U zP9w+}q*R%;TF9)x1%6Zbz<0novIRVsb3(iI}56RH6CuTe+yZ; z8v`b>WD{&DCse`|w*CA4mg+JMwY|`H7uTOQh4tTCaNy&?qLZa=>J$&iw-t}JQW5i+ zgWwo2wzWhNN9|lsdw0aj$fj>=r{@z-9zY++rZ6X)hrw$+zcnze*}pBuiiJ0k0S?+& zwqN+nDg~Em0+`5*i2MFq)AX$79GhXOch?LRAgxyHb8T&vdg}Ft@Y)#Ua0tZqtd=1G z^kcE4pWT$5M6Mx)vZdE=7U&VWy#7G|7Zr>lr(@R6;|61w*T1Euc%KM1R)&_Xqd2Wb zCoQ&4!C1gNIF7DzY8ZPDP16FAt+)@#XeE_J87H}i_1=FaR<5G;-=bM zQ?ziI;t1)?9p9SpQ-NGWK*dsv9B=vr^XaLYVlC~1(hX3v06?W(b41elNssJM z)h{r*Jde-C zqocBLp`oe@wkBk^xd%CGV;)T4z)0bL@df_z)P4L8TtT9j@E?0_|Em4)xA*g(fNJ^6 zk^2^Q_+NmrF#k72jHreF+hRxmYk-#jN&E`KKa?o`bMPy4|0=TZ_sIYBEdTosA0sX8 z--rDh?|wm3-FkgA){A-jizZqQKvvV!n&&4?hIq^Xm zzAs*esH*0@$VPH1OjBi?&Q4GJx8N$c>|YCJZ}qQtYs_UEILu{=iwNtVZ+%VDWo1Lw z%wI3bXPql~*h?ztYhRzwj}zV>Ru2~!>5teC3RqTGHpgU!gy3bxBrrRT;B=9|(;ZHS^?5)}dF zI))LsvG=>cNn=+|A0IDwL&0!U^P8_PFFSqI(cD#=Tb=H&4@GOvU#|~0kJ512T;A?3 zpYQjSPDn&O%9Sbp36V+U`o2aSS$&aG-lmNs;W0 zh8{-&SUll&rFv07ETAnO)P8Jk!eRa*PhwO;g~6f4B2~n9WyMmtYLZxiaSR7u6Q6YU z3q{eXlP}*;2+r$<#>9pxBp<7Zp&YYIAi$3W^4eKoB^klA0=Hi#3Cu%!s4-jYj#!@7 zQ0+srX=v?o`@Vdz_g0U9kV}e|x&x$~@1C1vyh~%Nh@m`AW`yFqUot2&4 zIjP>D;R;LT8^|HDhd!m-FQ^yh%Rgo$;)cMXYa)sW(Q7!K7OQhnoQKCZMlQ823u{ig zbj<6m{B?JK@8Rx=|DGI-qn+M0E-8g_C))=r;~QDy4-pz^)K+2O6h1g~nPS1*{qiev zItSkB#vkH04~dL~Sx?2b2F5sG6?+=CyNviUh@3I#) z_&4}2+tp~o7bkO$zt+_@w^-(zwEBXqSESz{C7BKt!_vROtc6qV=`ja`s`vQS?UQ1~y%w`N zvms3f@4jM@7$8gg@@Y}>?wl#}^N%2**(an9b*c{*(D#mdXY2=Ia5* z@&ZSQB4|G(a{m}9o|Ca+9y!@e*m{k}FBY5dy-!1@0!CP;>xU0fb*V3(%1@!;zgdo)!g0!N4ZUYoNLZ6aT3x)mBtZYmpX zZXx-7cOpr_S)=_(jezolBHzWt_jSj$~r>E3?pA&)Y|)xw5E*(q89y z;@ricaC^CS58up{M_C98i|EMIOGE8?WxD3?3s`8p^VL#uMA;0uo&<}ngTV>a3kfI> z4pL)q)cI-!}cI@52ND%|u-5#v&5LsGhF>f__ zip%+J?{VxQvi_!j?u_eAuKIaY}JsYNNC6U`ZRdP(1zJ1ObJ(ze47&}3kV!> zv>vaYrCtc!g8Wd=LDZ?cPK1C32}uJNe`s3|8xlPK1`xjArmu0PcewCc`R5?*s-q<| z)ohCA_(2~Dw$KmCaM~z4+wsj&PozP<3Y`TGSEd+^^!6UAkURR!Ill~A#S;u0+55}_ z62iq`Ax_5#pm!=rcC3JxlM%XWs9jh&SV5#Qbts2J%|TJA`+(It)wmy9=$S}}Wa%Qk zns3#}Z<}}b&d^go697OUcwzuWtU^6%aIDUKWa59m7b7N4ofr^>$pK-+KF9x=u#K@W zZ|c|mA+wr&t#LZTy(G#gBbU%V_7x#zmc9oG;t(kv8WN0jw2^JgRj_zoPW<#2IS zj9{Pv?NRaf)?EB4qyYW^>wdZ-N1(@@8TJbo2ltD>{t?{Ws%*nJ>k-UyPXWgT(V}EQ zIt%J68np{<2e6;ho~5MPAG}asxLy;~fvs)4w6v9T@jgd&8{lV~k@i(Z= z!f*G!6r$!d!{kpM$A}{^u|KkD@<4V=5sq8j0Shuj^;R->%NvFa2o!9+Axx%p#ByUGu-ZBU{3H8qLBqQUG=?#ZY& z1RGl-4A_I_k;Ot4{ghFk418z4_6>BXzb1N&uDPr^5qcQv#4~pgSTx%QQ&oT26x(#I4WnKwm695qroR zE;pObqfR1L52x9z>=1&chDn&ZEEUcGOM=OaH{&t0 zXyha$8fqJ|Z33;mK=Ww){F3Byaa4pTHYW>Su9;jh2M&GB6lKm}%$lGq`Ih;4Vj?Du zKxLIHS#)CcT;tYKrx;HxQb&Y$USxW;b!9;L@oJ#*nB2}g0{L%}`u8E-+eHb_#=+j_ z=LAMr0<1H{Y;8e_o|Dp})ptaOHj2pf@p=hvMq)u%4qLJI_km{eN&Tqm4WT-7MO*I6nSNYanDuo`}05c&eC6 zZS6o-Yffv@H$!(Ec|X}I&#s|}0f;@JdF*K@&;>%(GQzCs^~oZ85QYnnBBsBpA>{KT z+G>7_;z7DA%_3!v z5B{LaH{F?88EF#CO?3x&pFDB@NdRh2AX0gQ5Tv_XNj=EKJM91ioB{xWts-PK`(%R? zxtJVEc9#=|@gVqnQ_Y={=%H})CjOD^xDJoH&S%vjr(QzKu|u*_oElSj7IRR>C`^rb zh_*#Z6O4PDu{M7S77I=1P$Bk*0U~OiXVv+gh8yYt*51FcG|ON~B_)g*m9fxAt*)Mf%z%-f;x&rRuaM50q? zM26&_)2Xig5oW-6Kt{8QAy`pel3uKz5hNjrt$)q~SE=&IdFW2Z~X zG!z^|B!2C8K?{^iejcCk6kJh%3+Qi}U|Ou*6M6q)vv@D*n{pHg&KI^4x^8C0!9lg@ z8GPoM;z9K*1>Ohp8vX5qCZ2k?1T3b|+z#N9@3e8h6;=2n($5f+4za)2D0RRB&>E?JD`VJDX=v1LlSKLRUTqc|P8CbjnIb-EZ5=)GR=M#> zyZxzDFVj=3Xno6PTys`8B{dIk^$j6))nmd#_f+h*Q+yX3)9j7THAmoSW~Kw4^%k$N zkTE7e<{V7&q@Qvd+CGXS4>D7sW(UO~s4#cOAe7cIsj@bKpBK6|T+`YCA}ALz!;~U` z=TE;O2>)rfk#A(Etpi5kF`K+=_Dy{+ygsE~6^01Pbr36O>H*}zJa26dJB7oZ(5A2* zlw1O!Ud|rLX8&cw^c>|56P<|w?Pn_%>ODq_)XL^i=2m7;zr()4u}BnGd==US z;&^e!l-h;3w2u^e;O$)OnJjm3q*TO=1dOb6m#dd%Ez6(sjTll!PY^d?yEzw8T$!VfYWa6=_{kK+pH?^_s*^ZAMR`$4xVv6=V}x12jHK-np{TK z{TUT3aGU*wxR~<4=np|GLRADu29tJ6RuSDa!qJhYSDKgViaOiIQs-Ky-GWnqIlRL- zu?7i&{Vf#KH`@oNJKx+f(7_-;Ac4f+0yw!Z**eDK@d0TGEC)5{d6#r zub>-)E6ErZKx|ozRFv#6+-PJXj??&zwr@U{6jNU67>bSN2AoS?;2NNo3d32iA8C)kBbE_P5r$N78XP}LEk^WlX$KVV&4egZs~LY%=0%(d0;Cel%|~~|2`E6P^BDtcOPTsm@9})J z6d%cK%jaF4xgs`{6|1}bESkL;YBF4fD-<;;V#Medxq*}*k7{q5%Hb`R1&3lb^6{Bm z9Dl}_=E?#Z*A!){ z6q6gq5`gE+&lQmx0!sUd%SPoN1??qA3O+^(0_Vj@doHNZsCK)2jh^6hRP?~g@M0a5 zwDhg?&ZY2E!~*>M9GUaDbxa5YU9^g!!4jbob);~HKIh`B()|XVe1yff@Ke>_L09jM-evx@%gils@OqPkzO$)4sZ z|3j*#+YI$%($@vWmIK@q>ybn7o&((2#)$8s2gvTZe$uDb3`iSK0Zw&H15H?F0U?)- zt;gTjWyO_t7PHE&W|bf6)*;1f8+xHQG1RbN>~}U&;TU0_gXszB6rZ$WZ^D@135o|) zl~9VR_YnNyJ-akDUy6SVj%12m3y&vmmf#Qj>7kMUSHB@mjMs=K6~t$R9yi=^4K&Bz zgm8f~50)6$-ffWWv&z%C~LJ|f#X^{qGaanVmtLdH%d3`q3r0;(&YqQ z?GApJQdLj0vR%td?S40#dMH)&mzo8iyF{<}Y zjV2dMlw$sfbfQU8_;Y4zwOJR~&i92nIXTS#AI4|DD;WH{at!~6Y8@6t>E1OWX#^8a&~mWh`3-^rscaGFPBGRJMfJaZrC8`;ho z-AHz$<)}yVUpiUWg`ht5{P1frI7c&&_M8N1dU+Zmi=(U+jEyq}3+C4mPf{CjtJ5-2 zs_NA4@cKBVPQPj-ce1ZbYy4I#>+;wED^ec+0@t(g7{JD<*R*97X?uN{8$+$?d~aVAqcIw|z&$vP6!xju+|&Mgy&YI?(%%Te&M1*3fU%Cp2j`MIT%&p35zyuUOb_` z^S(L!`Wgi%{PB(o^$BGOUStQ@?m1B?GoFgQMh?7v4->M4(1@#}uTv)c``Mum#DhYG zNktQY3mBVp0oOGYHsXy^-pPN=t^YCZE>lMjX!#r|ok>#xDTQzvU*XWW#d|nu%5F+D zhywI0$k|jT4sM;wWSQhZmGrgQjETqJa#Lfa`>z=7qqN;myTXjYEtRfHZ`NiQI>O)J z2RrC?LZfexM9Q0%AWyfK(%?rnqXk7%aB$h&4HasY!{Etb2Oqe*_7cF9M-Vp!W;>dZ zOvk_kb(=7Nm;#rtcFBZNEM@_HhJfHv0H6XBej92qgS*llTakHs78%!5l55_hL-_bN z_KLL=VT62&(w|Ln^kkLN2*NP8wL)-FZ+KR)_sR&*=v#=OJ8vSqHvs}gvOL!qt~dA) z(cPr4r`<&Zz|(jIH=YQf^$@H%J}OBn22(u2%Y*pBuleS$_G;A4cQ~nuvcKd#mr2Nj z5~=kdo6XR9TrQTH-^2_Q6ue82+gc7U8x-CsJ9IY_w>12{>yYU@@QB2%yTuSYe7-$ILmX$7m zJTVbT4&4!5bp|EFf9PP7wet>xB0Fb=d}kTF>x%(Q;(w=XIS9Zmon=4uSbKSe@5YTc zhemEME15Sku6qALLtP`HY?lnLcA_cOKtv_*`*r#GGEE>-4-vNwp*A{z7?&^x+Hx)+ z!vot!T3P`6I@Ys-Tib!wSH*F06FIX(J{5TVX9#k|C=divpA{!!yx?_=(r&kL1bm@J z_F^v<=pORvL$#VF5@Vet7EpveE)oe`54b{f*<$^gD6uuaU5n^Az)r)+ck64Gpl&a$ z84g7QkS(kYwC;%3gQs7EG|`>|>|P_t`AI0D8LYv-z)AEhZk8}poa`ujO=8F0lBFX` zZt&{lX}j@$G!XQ49Wtnj>nHQ`ss9ZnXA|Rn%TK>7M93dp@oM{LHfN9bY+-+Z_ga9} zi<#rmW`%cBO!FRkE^i%qAi+KUG?4PZS^@$M`=04($}x_u6s=KH!rN`4{3f}2xbVXx z&CLnO@V@>4H*r1aDC@TQwL{EhHFFOU{|xadX4M1;YunQW z{|!l~0=j%A4?zxT{O;0$Zj>s=zLct~4QB%>PI&J}8T?Pwk z*-fpKutv3BLe(6!WiJE~Mo0w1kagHNsL>#>@B)7k(S) zOm^#J4w*C;Am}boSCgOq4am7-UJQMXmr{hF;USvjS45M(B+DlZ{BM%zWq`;Kkl7Xl z5d$S+5XfXD#V3%^hoodnK0IHNwjABPxhZF4m(+Hg?7qz&hen{ER=Dwp^ z1}F&H2%Dfu`LXjz+Ua|t!rAr3fc2&RELDM2jH8IhI=arq7uJl$2d5>m1=Kw%92%|d znXnpaO%nf!ovbsCU;r+toI7{ROXVZL94;sZ9g{{wc-Akp>_t+H(xw^0D}0gfdl5B) z7XONE5T*Rlh)0R2$DQihl#B_`k?ms+jETLg)%9ESq9p`A(1`u3jdP9s5Ff_gX4NS; z5QMAb4{(x9IEn|+c}{n>jYUt1E9i-%2ZS(Wm`T6_7Cz_5OUf~-)zKX}6M1b>Nvf=v zE#0i_Cf`Q6Ef)~JSEJ6zoVp-{#p*?i&csiNMLwFdDBl~=bfO%o`fX?Ha>D}}_Aqm^ z$#hePP0)ms)3mCtHlUi%GdRnTMt#U$Js`Q5%sgN@9V#p`QldqVF<}YNxMcI%KSqlh zr@Xy(w2X2f%K(4O&@q|M(Ndy~tGxtEbfI*JDG{>{J*?Zz3?cuWbt<(gG<`+=ch+41;+i@c#Wo^)Ar^i--G!0}w zZ`#en=jCWQccb!& zU{80oQ*71qYLVN7QryWi*mj1~M@E>p>2FhaRdV7_6qYp31`!gBbf~wUJbr2r=j*fL zWrdl|pezWIQ9;gew8P*d8Ew5p_pNgSXCM>k$qMpNYq4P`AQP+JPY(c>W$rjT{F>r% zG{jh03r~bIeBJ@3_GI{FH5uG?R(e^gwAHwYm-(mK*W zh%o%}soVTUL=q8ml%zODbA7KEzd$1Rq6;lHv;xv)$qrJH3YHuII)?$?XnSvVH`-}q zOO;0XVIT(sj-f*dSSPo#9EQg?`{uDsw5{im)J>gbEMs}U$lxHP0xPKRdviMnWOHyblTZ|BdRo$@nW%pZLYp&a!q@Bj%MLz@8~0vB5B2`pS`0gv z0G(Jb)MRiYN&IE-fcdmg7nhh`=bVlkbI~nF|Mp0 zL@j=X+M(}f0Ci4ArfYK)e~nZ1d&=o_mY5|asc>~V6R`yKrvXck6Ck-6I=BgIGlIxH zgry2+_BIRC02EQT5FXLIPaM^QaAyigH`oXVUm zoe2LO7<+eh)EhW$tE;*OGfeX2M-#vnT6_|AM^J$F6;LgumVLXzU-nW=si@e$NXK<1 zOEUmH5NV*iK z+<7}(Ex>fe)Yt{GmbeC+#imvd~Mx3976u zm#j+@MNk%&5Mm7Ej1k(g3WeJ6+I7&!)) zFj`}k782HUe57PkrTvh6sHx=}_dE>K80mm{)!heReAufOrXCB}#KQd}nAe}H{R*me zW;-a`0KK^O@YIa255Mi{UaEPYN11J;s(hr#W*B3aO2jR|IS+(&&Akg`Nv%NmLq9zR zcFo4wgm=1Fw3yPuzyn|IH#yAaTqA_(CF<-xWx+;F@I`f`ng-8}PHn9Kw*YJmA9z;cdd)Ld zRHEgxOK~eBEsOc8iirQk9+YG*YOCngtoU-(T{TIdEsJj^tikj>O(MQ6H6vc2D7R2}s#x@ewy83Di5Eia*aAYjFJt20nY%txzw|1Xz ztGFAdL?45G@W&Gf=UdNjkn$CdT-vR!>Z)5i&?td|#5l9}-$+QQj3n3TKiNLfbxIk3 zko<`vt|bu~lJbeXn_DK{u-5aic0zlTS+>uFr3n-RXmuNhk<;>7Q}Ox}Ge0ame4?Nn z77)J9ae%hjGJ93O8pjQa(l{oc2sE$fF#Il4aQ;lUV0xD4Jr6ogfp#p+UOE>{dyE-n zf7w(RG6Oh@xkU1#FUh(SwUxgkr%$ z3@BbeiDO*XgFKN_%}@fq`y_3gU=V{NTn7oH|fIzFt% zg^RE$>=k3ABhGZxL)W|3J|H53Vjprld%j8FfmR(4M?$BQ6p(NXeBd|%gK=-J7ccSF z$D!^<`U(5K+9

O(&Z^%%3!kDBSUC4$H}N=Di|OG+0cVjmYiGJN3FZ{dY$d(16NJ~;S)fHV6KmLcCD{|kQ83S3T;g^GTtP(l4wAgx?3tyPeIfCG z*{4DGnkB6QEMR%VohsHO6Y)3!&H^&Lu+j1Ky|qVA$~7lP3CG4nNtK-bT8YWKDV~5q z9r=77ufjOnr3U)irS5GcE9xcW1}C`=#C?mNnK4v&i4^kSdX)+fbJ(I)*YVd*K`}!IU zSG>wa*heIPd#nTtkKc;ux{Q>^Q6*`_7Dud3N8CgQRM$1)hqM~twC_JpzXCnyGBM{j z8|M7ODJxh+)K@$LP9s#il#o@k!mE`g(VWr7u|$beIVH^@V+R=|7sxe|(%YOLJ1&?0 zBsk;ngii`*pHx-HVKFk5CYu<-*(k_dIPXnTSrp@_c8y@lrGI(=`Uakq5mtd3M~+9h zgN`Ml(%>Vh2u~_jGeCZ@d;pP;3BsHU;YWCMJ;YcHZvyJG0UX3ie&^kMjn;};fEvq5 z%%#8M30}GVk6HKZ7-o``fUKmVl_B zJLw?hA{PIfbyYQL7UY`>UcS08F+a1N&ajW;!2Altf?=-dXrnSwU1pvHNxW|yeMN59 z`{kKpvsVD5WbPxulJ=DiTBj$t-h)8Qg2RP;V0*}gR^4$mv&_%W8~>vVYdG$W4Q`Kty}FAr zKFg~{LCTvy8GdlSX14R~M}Fc~f4@{wx8_4`S{oso}4|xXe0lR<8o9 zH!`EcFra5r*LQxs&aqR^>R@M{k+}P_=VGW&)8agbJ$X8ujV<^KZ!;umLk*8m=61_2 zK8Lya2F0{BKbke5bO1yO1BWe4rj3s~MLKtGu$lJtR${Z8oG_Og)2%Ltq@$${kkRx^*f|=F7Wo>k; zKykW%(mJakh^I2M=;J)Ug8$5MO3_0#QTF9sqg!M)E4`PJk>laKNRNmmKMO(Tz0B`R zrF6qTaS>sq(X?|ow|aDo?N!wP>S;{#rU3XJJ1k(N-uxp~KCy4eEybpmQ}_@a(m3*> zTq&aU@TmTlgQzLj*Ox9>3qRO)w)@AI)l(ZEKX-Mo*W5fUKTEJ{zk8o* zlFS`RZg_GLo%8+~o|}`fEIKiS-$Bsy4X*CYn5( zNU;Ak!(fGl!z;IL+a<^rN*@Bwa(~gpiEJCzE*@Dh+;R)JFBM*Si0@nZERtRcOm%M= zI=PeDtQ7t@DNnHQ1!_l&ZSxK0D7j!)9OXEOo#vmNa1HYdel z`{Q8u>YmGy9|7D`leS`P0!f2F=@GDKb#+_CpW#xyn$w|DJm;swi_62q37pogCw3?I zHuE>DC!8wnm%(&w|F33?_GJiaj6Ekl#?cEd57&>=iL0*lk6YQR*S8HE?XFequjvaX zRq6K$?k&#`!=|t2hga<{2Sz9MlOblQfzK{)kB`$WuVqiqk5W(Y%`0Y^h9EMmUD*7K%Djv36lU8h# z=u3JANI~)6VE?NE>*qWRxRza?MHFWNl%Bkg(lWV*#8%MZvh-f(`AXam}N0D zGcz+|i^*bUvSgvf%*@Qp%#0RWU@@~~fo0+A@1OZ+_Me&EcyHfq#B9Wrx*`;+>{8wA z&b;T|n|V&2GJ|b)hFf<4L5W+@a58}(&x1l2av0R0l3aL^S>s_hzs<@Q4IoF`oziz1 z+;w=XgHn4anZyDaULRBqqGq%hCw3yf8qwrNr7B}2=5VA`dRv@*vFqA`k{#e>Gxt+$ zkn^r72Yz#Mu?@$)&S&9o>MwK$~KzqTD}46Z%nHQ#O3 zSfv93oK|G8BP^!Z*w2qV?nX zd$bEGO&E(x4}&G(Iy+2utMlzCDt?dFZw(UrDzF1n+G}XC&C8uObX>E1@*8Z7Vh|Ga zjmm78;t>t&Z#SLYJP%N^8|Oz6P}j3N&F1Z!SEOC>1g#?CBA3w@IQ5TwEbm(L+4@Si%3(BV2Y$FWt_Mj6%ZJ`B+Cb$@HYaLIuvEbyH(}$Aw2pDGpU01l z*m*0plRRX@=2ZrHK{^LDl*+LYpalIns>xXSM7x0+-Rqa7AkxXV-7jDE(gwa4^#?l~ z08lurvQmwhFDAtN`=2-b@CjF@ji4g;!qgYFk_6IG`p_6^njaAbCpNKW>jz?4rlc!V=1m zh?S|E**_&`$Y*BLFSU+`8e^IB3~O#nQ%JX0DHBocGy@jJ5J_;Fjiyl62BgG;TBvvu z1Hr(YgL4E0qP%PF1-`|96Gh`F^-nrQ)Vp2Zl3(e@)AloVrD9(9p2#$9s-5>*@I|~X zK2iE+d?$zlQa?yX*Q|r!lDCJON;%Q{ByA;9;3sk}SF~FviBwE4zo-k$s6J1;`~g_2 z{%$9{?=|!E(s(=7MYbQNSA$U97rIHLk~}S(=}1)u{RbC%Kbrg5js+U0&?Kf}=lKoiIKA!cQN}B!TPTk*urtq=E36g&IECPC1P9{_l{%Zf(9atBc3OZ@z<^(EeS z>9DpT0om0^yH%-*$wyR0@+?HOHa!21zCN1Cd>cJ!mb*a(7`bScANVDDI%dVG>Hz0c zC?OM*UHH~s$sZMTwMpKh^zH;ZZ;CDfMFFb@i=(V*W?_mRuuT6^jc`vW@#U{hzg zJLUbSFEH%WQx~3J3k_Izsvu*Ng>og%A@8VTztVjH{3*iWg_!pWu(zpzu=BQl7SURO znS{Lu9Fuhd#?IuBMS0n?LW51erj{Z`rYG8$>@IbFf#XF^Aos`MtPN)5(fDDPCZZ;c zTLr70F);Tdlq7@;^Jn72eK-k}O!vHBuqL~9iDK|q(!F>Xy4Im_j2fJLGa@j!rv&KJ z6ZZ&G`d5s5K0B0?pv5o%dU0c!1k8Nk$C41Dq){7reU#dili8Of(q$Gj)^XD;HSliI z(!v!Jx+kuSr#Q^^ip#|^dpLVJN|cL(#a1YLo3>ys)Q)u%r;=3)7qraeAbL~bk{!7c zjY8UR2f_A0q+~|Z@4zJI(BvB9?*XX~G*dj9s9vdhIrQnGZKt57O)50Ly!FDoVu0 zy9;x)$RYWBl*W-1>I8L0cxrj*E@Fj;j#3T&gJd18G<&+TiK#e&`UOG~XN`EX)NX_5;$z$Q{2{ z3Y8V%!AzXApq*Dl_QZw7^Ljd#c`XJEmh&#?lBV#fAC7B@^zK&l6>es0iu}Z_QanKr zo)UIKXH9x~7LaU6{I^POYcCGkp?8(ha;?*viMtD3QfC!T@FEydCaT6~B1@W$l3$hM z0EO`1Vw%WN+#!OCobr$i51%-{DN=?~!)_ZT(KZnqKz_YQ<$v~o45Xb1tvg7^VyPV4 z4#~_{M2%-4QR5y}Mt}E(ECBPXHL-tkokk1%jxo(xaN~fogEMUq(`2$p_udkP*_>uQ zh4v1`$~CFbY>x2WVgvR;$LLg%>}1j)dqBZKyFc8JhX!5U44Q%GYxWH(eNE|oQZgla z#ggC+AsEk}JP4}By*rjW0#EIqu#@JzV*R0hhhb>RO|USS!*=u|W6#oJqzpKwhCQVw z8L+Jvw8P(}G{O)pk8ILq ze3h(Chm=a`fkp$85e zeq4ers9sgCwRTpSr0i^*XkV;2)gs34g7|4>%`n2et$StDKW=bDGIV@B7iYH-8DW%D zgt`Jq3?5LgaEDv76VW~SNU`-E?lWo*f`}CrGR4Q*(D&&kCZ*{y(!kYtcv)HJB-;m} zwVMHGVgrTRdZQ#4Z+fM)0|>`(TGZtAr1I;qB@cK(;#f-tR{FN zzeZFRqVG*cCkF*ia7DEf6-2aEjNcZ>sgGo9Bt2x3?ykXh1|DkY11a3z(66cWRl)IXEVuTuRg2GnpzU@heZot|EAD-YGdwn5QP` z)YdXhHSZB$NK^)o1Iu#ORDC3>&kVJ&{q5j;XdK-at6OD7o-@hdp@9OD*=d?e1j}2L zttPmzDSaNDaVB4+0bWWA)Dwl{mEb zhf)EXBb@dUme)%GaeF9Fcl88XjYO#eTHz#eUWgc~cB+(9-w9pQFR&x-QUFD~;_aSs zqkAu=5d$4RU0%n(C+Mg0*kZ~`+!e5u2r?ypO$Y*&F`0%-cV>pFHH4R0cX4`OR z1v?P0?bY<5Ta%HlbFZi5ye2Y7>p}w3DJejPYFoI_<3&AL>T&`0=9~s8MN%_3areIG zOwvtjD8tu1Tv?y^(>1@^Zta(>V0TyuSNO-g-ZD&iY*`f~6C*5*j*Pcgdd9o$$R7$f z>w@UzpB9v$P?2ZmMj&sIQ*V_LA<%DA>)ZsR)CySf2Hd)$rmtKkn6JPq+}3JzzkVlm zWCW5DeZP9+>l2Q~C1!@>Z(&o4Mh;qm$NY298I_@gu-(4fNK(gVp-jjxTYu7Bl0>vv z7!WdR+i~e+t0klY*CcqeYuIdn>g zELHP^#&U~Dx!`E9%WXEiHfg7fN#8Z>IC0$#CnKcOY-7ejN2lB^J$}-g9F|O+J5zCb z{y;<7d6YIS-xAFCIco`OnfzPW2)|>!OJ@09tsuYHjLuvgumu7pcz9vchNdq+l|lkI z#bnhB*v$D%PVMD9tzut$8~{X>iTQn&d|k3zxF^ugi9YSZHSGWzFl8)Ed@bu^pZib1R&jRYZZJI?_21T10 zvr}&TMQd)vlxgQBC*`H$Kib9S6iT{@G83Ja0%G}qj7*!N)Heq;iE~)jE)VU9U$^JU z$`E3`ocrbRCF}ZsxJwaG5+?MAltDT+7`mZB3g51eWJ1+#;urJ>>IoT_nhe-#NlGBh zwum$EYu|i5Ai<{DBIojl%1VMNQ5>zL966&qwKvBxc&Im``3~PX-4~?6A#Ql>FB~(IBi1%J+DxlJg-Gi_TQ%qeiT}Ik$Mq zB3st|>aY744$dqhGRG`4$n+&El^gB)2;yqWO9@lYYuYQ|!zMeQR!DO*X6kCox8w?j z1ZDY@C$dZI)fEa4Qlz~u3bDM8tFVLZj=Wfv$|*l@rnnj?M&B-X(dSCYWq$ic(3aSk zdgYtQB(lg$%k8`(SuOr)+2U(OAFn(>Fao+eL^*G9PD*3J@^=o!B_4W+DpV8SR60Q= zmXcFTYVp?eq6ub5N&94Q*=s($hO8M^0v*i_cYQ=fCODTFTkCn1e)VBnUU512I-PGt zVJ&5Ap7{*RO7fLg2Cy;phhHisI}peRQ0zFg#h(=kBb#Y~^pvw7eY7<|rp#phgR9}+ z)LU0llUIE$r5pnO;<4UIj$MCO`rS>+w)Pii;SaQ*%4&{7mJ*J071Uh^fnSDfIlkxj+BR#2L4Ku7C)5n*$>Ix za&p0i+Yo{99~e7eJ@ z&E>T!!88bqPwKWiUtyVRy?mogJvPtobi$dK#0fEXHxi^qpQa7i7lY%f=44qdYjz&z z#%|u)vnZS#n`-wdIniurZ2V4F*O8&W4Gk$1bV}SU;@t4=sfO^p?FEeLAJIXV{Rr!A zNITryx33A$Ct)w7M%Q`wj>s9T;~+NY3T-$Y`*TyF7&9Y=klEe`aRz~e{2!NK{?eTK z&#qvAcA~f$bAVWC|2L(${G?b-(%@dCh~BoAqy^UjIYQL)+dKUmE3|Y0iH& z&PAjwXUzj8&^tj)4nlT+;S&|)?s_e$Uj@0<%Qb(KcjJ|0rz>nJ$3W-u<;65Rd)N1A zIR>HdLzmlLA1$~J-H1ZEk-SCO@j%SP_n=8|c*JxaW);izsHv_~v)d~NZ&!1P2NEGA zYLmBKjKTsqEMdoM$Ki`JzxVU9rl9rI-1x5s=l-3UN`BttS7k|i*~yL~fXKV^Q%E#e z@O<`T$Y%vxXC**!`)*v@;>5^wt}oWHup+gsuDq4saBL!*t7!gyhyj10ec3T55do#E z4Y9CwFgz~oF#$Pm@Fz2Nzo=Jh5AU9ypAEe}Z5yB8ogcc|wX=l<{61%Y+?wl4*KXj9HNrB)yjFIKW{S1glpF>)Rcm5 zOU2&@1R4bB+LVikp5`}p0JW<-BVQs?GTInNn79vt4@!+`Sb#Vo zSgB^$3fT+7Ztl%$mB(tnT69AV@cpge6HOl7~fq=U?Td`71p(@RwH?^ z^EgUIWG3KCT*&J(4AMAMjVbWNE$ErCikTqb@Ua4>@`)~~Dg#aNZN}%BLeN^|kfrJO zFwlw*Q@JKk>&M#FK3GsZAZ1hxNaSf1wZU%COO_5N=T!JV~L_m7xP0U3?Vp%ArbMgbuVs%TRRr>wCnsX{PI&vV@FAeWqSr|%Gyb4F&Qik5 z%Qm(mF{G49WjUp4-4}gcJyQL)?#F-gqQV%(@F^H|?xl3F0jzgHPr_m@a`?Rmaw}cU>1Xij z2?me3N%Kl)gzz%Mnj_Y`zVoa3@s)tSTwi`PD;=F=5+}OP`svqCeDQ8N(edftRY-%t&_H zUYjL)FB!?tG!UT^IlHVJb94Ycm+l*&ZG6hYPRf;dVK#d*?8@9P^UOWlLeElhpUIUVlPW{Ovv@h5UgWk%=kdiFjUy_a z&{oz>Zmq_)U31k&2{J0wek{e)uIV_R$=R}Mh9@=p-XG2730#L0S%kT%I#iU*2|9Bo ziL~t2d0y7oDqbud`%QF@<4bwz#kwh2#i@{}u$T5uVg4>Wi^#2=l`YuObGNxnCmlmB zBHj|ty&7pO%1ilvPjZI3p8q1$R*Y1mfKQc5YfYMhV|YjtBVQt))!9A5TZ=CKxdJuP z_^PGU-2btyEht09lz?2rLVv8R^MI{yxGbRpQcIF3&y%KSn%7xhho0W@ob7X|w$%p1 zU92s+1$J%UqEY$5ue+EMeUn z@%tZhz4+)7(a$CX#Y&KP_ELsiT&x^CR}S(V2c*crA=r!7SX+^_Qz*#C1aqVV>-udS zD#?Jlb2y;R5}L1gH^Q;tlkeJ>>&%}t1_{vhtPl7KYBNH1D1!wpQKGn$^D_L?eNPi# zdPA*cW9}>6-&+o`%4*w^>)*CkR%GnCFb6w#nr7^&V3d!nXejS%tHVtguSK`~U)%|9 zuey3_WoD2Jya}^&%DwA?Ot}s+abr_j-Tqj=ti&z%%^gS;{KxR)FNE-S*Y_V%#!ET6 z*n_z5T^-$BOwC zzD^u@8>%06{>NNg6S7zmLvFtjm@GM?3l6UZ(XV)H;x1eK#+Ycr;hQ?;2IdC6WdSp5 zmQZ7(A8O)Q6DG@AFjZ;n%y_c`B2SA?M}PLcO41v)IK)BJNJn}zZF1sd5aTIzFPd?i8S`I(@j`V(OpJe(A$5Xq^NhhqYf21QCn>!JXIqd5K ztBQrbAbI1T@GE0J~jz+1cdNfC;7mwGO#kTWhqKvnq;2PBF6SQ7otzRwEsgBtG z0c|J_MPI!i(W=y%u#NAw;Ms3#_P}|hY@O9cmK=nm{nEqTK;q`*ib>zNZw<9}$4q^A?IvCgbo4f5W|0WcDq`G50n*-r0V=CI7avz&8;+rQX- zGRqb$V4vL@k8kYm6vE5qHJ)_xMfqV@bFXU zqMQ6efFsud=4Ej;fv{@6C{`|ejM5E7RIbX&wtyOzq`jaNn8k!#FGqnAO*JBNb}|1#^cjL16Rb*f~mZuk-PUs2pP>GJUl9rB1o{^CgQvXObiD$ zod8OLumFXGqLCGznslj*@a`3Q%V76&vP8L3oged;Pzf6Wrc(lWC~XWV7b|jZ>RN25 zP>NxNahV?@zFfYHaFr=}v%zZUx5vX0`^={mew)kv(bB z3lPnluzT3{a2KHTnnjeCzuJsf*J|qow{VgBz;-F%J4nzC>lP;O(f}z>Yv*OtaFOlxLDSCf=rC zi`jbu#{m?l^=X+Xmj%Mqo9*m&Q=cx(-|m~ZP^G7mk!SlSnmpmlzEz_==^2uBNN@C( z(#yJZk0)VV+=j+dbUg2(3A-HL{hW)T@bF5&mUX$uR4Tq=@}>q!9z+UJFmf-1WTW)l zk+2s2-Xd|jJU?JxB8G+?V!;-iwM2OrFECYQt%_AS5momLACN^0gpeP7bN%{W0X|QJ zo+(hag+{C4U1lUc@W!WNJ;<*UYR;&Mca(3HX8uhS2EhEHuMVdRWT{{$E0+Q>U;;7S z3BbfSg=T&=*%~D*3>35P5b8+t_};D2o!=96~80FBlZ`PTJ$=y1BS4 z?GJA;YoRc+X^LbCR>sunZxR?faG?tDIXWkyfAl>^IZhAH?>cg*Qp9CcxS3emP7}p8 z+ckYVLZs+e#PO#pM3G$9*an0MevCi1rYf>g)))wvx<_u<*S zP2UuZTZDGC*%cmgW~e?K%;dGyCUQ=!Vken4?m#x*QqAV}LVE3ASA9W=IM+G& z$p`X@&k(Y{R2A20%hBs~9ts<1s1cW;YGi0z*Ru1HoxHfhi!%-TZsXC}d797OgJ0`R zR_}{{`s49N-?->rU6&=(RSp46E(S3pcQ2Xf&lYwH0gLP*(!T>4F}*G1*hN#B>76ck z1GGns3oSLvU%#B=`yje-6pEm@oklTP3a&gfK4~JsBeyMmZwyCD_YBn!GfiepTCJyK zd|?%3Z2dh~1Oerp?#3|kh<_Z4-WBUIM2q}QZjIl`h^#jF9&&I?@Uvp}r&x%v`?xGX z@7P+jnC;ly1g+XhDi#bjCls$mGeV8NXaUgX-8Fj9(SM* zFaKc>dP&&OrX@5_BJYHbVI3#>383DW z^AfT-{KoMX3OQqBH`}tD=P>r2a}o;h-dHDO9~z0F{pM>97Y3h#Tt(Q;rKo~J7ewMgFNU*I8eLvqz&)mNH#_V3M2dc>cB8?BR^N8LYOOKYwTX6|~b16thr z`i?kn%?&xIIVX8LpKGUY;b>n8khGS^iGK_E_ zvN4>yk8~Cqd^S|9EfL@(&NX&Q5xDpjW;s>jG-M_f=wHsDKXr4GYoViuFcwTX)KIEJ z{l`FG>im0e)YSFXcq>KvPoYgKVY8t0HdN5Ez^!G zh3Zw*a6U$hWWR+5o>&5@@m<%Z)8vwA(%VR!!m<+VZf}%N6!&A7NN&1?@76DKaa8u&}Wmv$!b9Kd8HN{)K7(0Yv{NFxxQ8+8bM%bFz_u zUjLL*`js4#8PT{+9Y^%pUDzjW6(Yof$3Y!X-!okt@q#&xo_&9Hos zlqdC!37RrHQ{;Qa`zTT3f_5UOStE0l2~~nb0`d1Zv5*>nBW&#RYp!d2Dkf%~O}o|S z%@p%bdB;tL$L!yGj04{T_CB+nQgX-2&a{|%DBrC;zb#%45$U)Z3C~*Ey=;ay5K&b3 z_4xW3MZMjf%B^>r4Hvkmc(X z`c!ky83k=T?e_p3TL?;O(ZCaV7lR%G;%mq2Sg<-iBFAgLHGd=S^YfvyypFH zC+ktFBh{}JmUBx2Q7&-qqm!9kDHUCYIua>p17!jn%o1Rh66Lw~jDutf)y;KdcH&X- zJF0fXzDcweW}qcooG5DVTN!p{4@$XFmhhtG#u&rVYQoOY*cIQne9X}D_{@N6Sht>@ zlin>BtazW0qR340kF#M0QrgVi`r2ZZnm(p1zF`0|)C0Z0TOs8mrLH0SWPh)vDon?V z53pqAO-GmHuoSma+NG38MVP1ATpYozC2HUvrCbkf7f1enQy=ylDp*|*dR_;EJz{TL zDv~jLdXjZ3B-lPDDK)Eb>Xea@UW^I(eotyFxRJj6 zt!Gwk7mxCOZNz!;dL(ok?|dMG4mWQt-rQ%D&`b^m!m0?NNk55YV73}Dx zB*T{iH(3)pxh9H3xy1M?3Pwv^>mOOY2mIE+*Kk{?=W;L|(_}t#Vi=W|_Be(1b?tFE z!aJaJg#Kxp%;rHzFa~c4F1+mis6|9Gdgc;}4Mzz9b}#xDbVwMAh|yMV-72^JOpd|^ zIg9h2D2T=cjc%}Q3QT(zMM_*v=l)ZU{Cng@{2;}yd84u7AdV4t3X&zpNP8HwViGoUXZ66ZSb z83x0QON`_*nILd!c^A0&JydGqi-zYHYn~oSd10nnosLDlGwooN`#b>}A$hOEjnifG z;dyI|o4m!rdYSbn9I5#{uAh`Rh@|LVdd9jMSC%5xZnW?Ol6D^i7GnRH)<_W zP*+Y2&3swgDKzRDyAtN%8F9JrR8uynjeh`#hGB7if|nVSy48t)a4N?=O9&enWFL4H zG-wuDh)tO=(!SOrl;p7;yU}TN;iR~>hy0TN%2ULtY*p}D*gGw=`5lu$-cC`~oGRA2GX}45BH%rZfhei`r$KBi zg}BhCE4kj&XNy@Y6)o{|w+bIPN7UI+SQ;hMBdN6Sd-itK&S7Tc6X_+k~*!P z1HA**jN1zN2Cwi1fl(#a`{=QuDVIYme})p8Z>9ybXrc7n_S*w4h08L_W|SuIIRu#M zG}Hp@%74sQzwMSQ;SDvM!__%XVEMhbe-Fa^neBr5XBzM{?AMZ@^N7#`{*CO0bpFp! zLj@ODzm(*IYHuI)%C#`e$0|(Gco9}6w8+j$FBy2{gFGA*f;neP&(Hq&fFHU3MyL8o zy55sOi1i@i-=9`HaVn8U@1mW0QJ>W+Z;BTb7iYBe`jBRTC!mGhs51!HT^`Q4m3Xja zv$NN7FdjUYTl29k{PXyGp5eOy2ZWa85;b}T3MUlW!23rU*sqnLFuO$>_o{&lpVf(~ zoGRrK_eT+-0QFVicP9I71}^Y$tOWEDlJ4Mx1~qNFuF`X6KofP2mb~y;NGNyLq35~d zKn|{1=`=>RIm|%*`O9g&V7`=(mPFF?dr=ZYC_L41Q!x2BMEIODGfKB!1y+@vjVxBb zTt{;1r_wK!qxC|OPwss&X`FjwRI+wT6b&~20ZHqVHp0j zF!L60j@I^5X`y1ceG`lG#;o>&VDOwVl(svz@0+#U&7VO*!6qZpSBat7gYgt=BlBUH zU-Grt{2ZQ1NdeWP5p-m^lxbI?||k%OvwHX7cy&Eo4Hw$aC7l8%a~hRTDg&M zaj-IrJK8zAs5u#%n*VDBlv&K$%~i$RMcmQe$NKk8E&~*SL zDij(it0*+Osxb_iGX`5gLIEtfSi=A&aQ=pZ-NYpj4ju~|2N#c$ikgO&j)Rknn}?TA zTtZSxT1Hk*Od!b;B8<}8jGOQ)y8H)VLftoRfMXA`5lez?V2r(p163_ zAx<80JAssfVWKl`%ilLa9!-3{?*h~R15jj71Cd`CNdu90oAw&O*mpr+`T;n#*;uIIn1rT76@Shda12alxrzw_*P>H9yPoXsM z068D|6Yi`?p7L_;%gBN>!L*&BTw6y3EWmtAIqL@nR8dV|ZQlVQ^P5B3S!`nD;j5m< z*TdD5XM>G(8@}upG&hw|nmNaW8y7yLjxjBaDPk zTR^pZ`zcAXHA`yAt=+-r6wtxSk^%V@cXe7LfaH=#Sf2py)c^cTpO}92<(u0FfW57- zJ<2hhb@McSd!)d|f`0-FA4*8uwgc9#GUfw7qf#x;1r0`$d)F3g+_LC+#VN@2YxHq^ zuyIZ1mYp>va8zT+j!uuq;G8f%PM?*mDhcsl z4q_i)eE<#$PPQN6`#u2kttp`K?*W?CK1qE5>NP$9?=j8!$bp~<0NH=k@E2q&=&!X| zAApkjAENpnfatyhIQtKPu>to7K;F1E@Sh@x<8HMw8@`yDBx{r9+8Sol4g_~{sc39o zs1)tk-jumz+l}#RI|pVe5=lr!`aDV1q-S`gDXm4rE|r$It!Lk3Qp&Js^B?x^rA_l# z-r8oZFMQi_npQunxMvOmqYK+CSmj=EU3 z-;Pwbt{XKi)YX+Otr!y)R?-(qh$IlZLII28U7S-!WyzTqbp7=<-$|-J0Cmj2SU6T4 zUkyG0a0I6wr8CF++Tn*y3B_&T)M#uB>3_xvWdLUT5YTN`9DQik&!u%tVQ-l?XAVtY z(_A_Qn+vem2E^(UoFYXim0Z|k(*#VtnNoJ_m42;AH6*1Dw_6Z=9X2Sp&RI)@-=kC z0JnYv6V|HxV>DVfE@=FW3`uHbk4xOVumZfxY*6%ncEeaC*qcq^qrZ$#rIM5Q^sx+_ zU#bl4)2_eIGx_jVtZN%glLS7xqCm}lxr+Pp07HNVqZ&_ll(45(W7C$p@@%lTv98G2 z-PVYK`lq24KA_8a{FubF)ipuUw(a4~@D>K-PJTR0Jz;VxL$5tW4JR7fuB`k{RoST4 zWMMUJh@g@d1W+G9Bn>{!ShNLDHAbm5&L6ZkFP3DkKaY}WhdUG+O~bzL+bQfEr|wmq z-yh%v0v0113OqZl1Oyg;3XZleoGF`^Od(n& z8kXZCz0v@{e*FYT7d#a`EqkatcJlLI$vcR+^5L&}3O!b}x6{@c@Fs zV;IkWV;$wozBSoUz7^bRD=|2$D3OE;94Ee5lqFY~&hq#+ULgI#E9iAQWqE6&So*o- zu(YUnTUXJJs%=`4M8`}+N)sw2K08o!d9lb4#k0Jgtl$=Tk9!p%_*2U(qOVvyKLy zy?TzQ1-90qA#aI477UIAD~pi213hkd`f;a_`J=onqo}(3T3Y+&T96nW#qoI^bg9sd zJupcLlI=Tq1e1T`l-60^y%P2*pR2eWdAxVMmkF0W5d$0QSAdh>Wfu#3SdtgIk2{-9 zsui-@^5H2UY1g}rP?JV%o|p%lp1%ABX0Pj7r>5x%^zhA|$?i!*MaiaV!0VCNYXG2# zgH1;~bdN)7TY9(^ds{+RSUn(0(cknK1uF#Iw>BDDDQy>Z+ zAk9?EIcqWmF;qnc|Er;r|6H_DZi@w>K3~`TYXoY!JKlk?jTZOK<_)8`wwmLo!F<0p zifu{FLk|-IZdrRnwq?n%a7`5E2)`^%d-2$EuQA-D1L z@b5mBIdY|ObbbkEu3;w2Jy*Alpzi2i_cKk6#+iW3hBl7J4j8|3Lt?Y1T7RTn=d!M= zWl}E~hyR78;sV1%&WZg>!B_SM*LnQ*-U#w(k;4D@+-mQA%)dUJ-+chM_9!KF+OIw9}f{6gdafRaY>P&b!hRB5TNjI z^xedt<=s<(Bq*MzJ^+a{AAsO6^$$P+H^?3Ktr)&0$gQlRhB@#0T57&8_4U0VV*iM- zBybX^pCC{b<<6K&fl5!@9t7-oWo|@CcEnx}Ct-)zmQET2^3qVM{kaKT_$?z1|FM5C z{0GG={pYf3{kPAV-(^Z@DyXrSN~l^vGe1t+JIWF>V~rhRYT<$+=6%=L*1$fc6cKA1 z()a;dPj8Ce&=!10!wSbsMKd$v>EI0hbe?i_cN?;n6}g-3Au~lbh~6;UQjq%J^j($z z-gytZaz|hHO>75YOS2-!_amL0@*)N&CN?K9kUEtAz^lUtK+~cyvGKjtut4XMshj_1 z*{$hts+=*uDOftOj{P+fjD)}}P>BbSdB*V~*|1n+kJ$!GCKDj|_)WM(+gy`Q1~Mq& z!usCB7vxfxlOH_%m;cKR>c1wL|J;FHVdIsP8)wY;UceK$!0dnVw>Ef4LaRkyT{VLhdFYsEx$4gGjkN21;U=&j0FU+Fx_=+XfyN{u zXbcM6g2JTQzI!#Tzi?eFX#C`QX1xz+89vjN|1C}g6h_kgmpg0yuHWT|K&alR>I3kG zZ*2hO`rH4P%Dv+Yl;H>9WJ3;cLQ@zCIDQ39GbHkkFYu>*&w5CpIPhueT_kn;5p&W1 zz7_9;1|0V|bOk2!E08AMJZF`z{5N$8WzA;KL+4N3?-)02^b|_C%5$+uE}wZ9ikHn7 zhOs)olu=eE>JyT^t3O9k|1yg7d8i_b73RD6D$^v$gHMuhJkPP<|yzJ#a%g^_EGvk%E4Sr5xPLhzQ3C^V0**PxO@?0`uG+usgdP8r_p zt2^(Y4)MXtCA>9^bPwUhYgv%R;h8KqnD%W1A^pI(nlGG3seHeq9qu<_fZV!rwGGs3 zD?itHf5}Q4Q|(euJ7v&^5bO(>)qv1_OAXnUFV~r^bqmxo)_|i)eaaHK2o;l&l!XNZ z{Ws@qG!f{y^XHdIXX~{OfZhj4OjTVgb7Y^;g%Lr+4q>W_mocQ}=epga>MUhEIjt-& zVi_+2 zKtWc5s3e98*k{4pw9(AE5T6;24N2ExTQ9rE{=AgkENkFE7^Lx1ytHW`{i^MhpP*F{ z@X49-tk!(d0nJHdVqIyJSUDa2{SsjF%5wX;X5XIb17NUZ9J#(OpGP&j&Kh5yk2Vs= zAZv%-|Hl)&YyC)w$g1xq?p1i6V=sh4-aJu5P$KTG{uKfBNlRG-3ItdF%bovbq@2*_ zb%Fd@SdsrK-3Q|I2bd|m z?T(XF^pBGS1?YgPCxL5A;({I2K5i3#iBNyRC{WYlMs!;v^aex;={_>Yj=r>>6+=`yu&Vc`TwEry~Co|)_mbcK%yWy2c-ce z=Nv^sgNPuxL69IcIcFpZN|YcV8Of>18I+tPDLLny1Oy~Dx7d5m%mMd3=gi!>^WASh zPyeyoqN`TbTI+q^UwDgd2>dwY6WPPF@@M>%w^|7r587V3xIZ4s++&D!w6`wmxSd3p zN1;*c)4Zemi4G4RnoB>+h|qHNar^RUyM_E(m7VZR15IHibls83n9E6C*@Ek9j3HSC71arg3se{SombA%zkn0oi}iNd|^PRWV9JQq+@BXWFU?IlQW_!0z{ zY~Nf(cr9O;IvmZAl~oKLm=ng#b!5tceLnj3JmL!NpyF7~61IOAwu2S7 zv-nxzovzg6*LNkpaVi{YHd43H4TNkpoy75Cbh%a7iId!O#_RFYU>eOXY~-)TN%4tL zpKb+o3LuLtVd{FR?!BQV8{o>H7^CM5&LSuHh$C zjF-^(!ILMBgnMP5eXg#LA!DeCi}Kn@HW)LUT8?KRUe9U0a3dm%UA8frG; zFNx&DiIN=GPhHX9wZM+AqvTE;mk#@x#KZK0%sN?|%aGJbPASF?WPFlf1zF>a*A&t9 z%r~p`tPIR*;jI{cL+&@F(`Sg+x+STD3jP9!E~xEfZp%_z(i~===m@SqBXi?8`e<9Z>STZ3Na+R<#|J73(%yBeiLP_(ZDw#Az&~)dX3fNc5Ws zr^&(v@o1~|bo(d1Pi}Ww74IB;gXdK`fz3id6V+W!;HBj9LbSAD_eo(-nRa*PoEWI< zw~qV#GAbO>u1})O<7bcfA~*w1g>|293}`Waj+riea<8)N#LV8mNo1~g345qky~=am z3nR&3f2LEJm4omr;}CdHJdydsP>EN zEFS!pUe3fHsz{tujH=>zdHGa4GDE&Napl}9rY+nG(O)9(tKIvID4MslV#u zfR$f7?nahbMT#D_qqygz!X+TsL^u;zZ1r_DTUl*edv2oDY5-jQ06UlVSkeYAP?Z$*-RaE3k$$mf%azzTRBx41^YC0+o>mMvbs zT*SIf?!k^0oyLZ^z3vLEh9r9rR5MpNII3z01NnSd2#<1JT=w0#(BgNKU8`|r?^&cn z&-R53XR~=?5M19^2MQ0-A{T~Pz6O6Rgc}8!b{m@RcTOo78G@D)vE%g}95i@M7Dbsh zgjXVk&tyuyr+TTDKNO39;QpCojQSSEHY)!6_likTx8cG9=_YSJe2Biwh;m54qeiRe z$)rX!+MuDd>ii-i-KNERpu0)iKtDXZl3FCBI;j?7LtUeKL_Gs-#U&8_h1{4D1e)?|VLDc#w51ZO;Oc4!5F{#$dI*xBc;Uq0?R_Sg(b(CHYTqIL4 zo5Vw+wQ5wKJ-y`(p{wp^h%;RLEPlqfsr)6WEq5sS>!f9Q!kB-j;>YQ_djaTuF6}T@ z-G;*8Lwsk*uS*aRFr>=UY7|naO^*tR7Kh?u=ws%jvwK#CHI2x8bH{AB{rWu&vWG=X zGS(wI@JM1Ub_-Uw_#$=C7cAz-QiWCL7Dv&j9&T}Gq}2ppf+$Rn7(gi&p9v&l1xv~! z=fJE*G@IcdwrRhrW51Eao=m3{aeH_tz9C7T;sw#E8 zNyI(hqk%rj2LUsU5`%KLiIS?SkfZ!T1YuLpSw#3hnNx%J)Y@~?)<*{mGbzpKd-LR* zq?1ZO#3(+IPepspEnq_ z8m|BJcc9v4d?UW5dZi)ICAkCixP;HxaMwU1W}aDGuEqI!qdMW+!Mos+>_( z4<)$$C?dupsRbNq-#CZq*bz{XB;TU)R(16Ia&6^4E%8W}fIjW2Ye;9?q(Gmh4F_iw z0Lkw9x=11X6>%>?sNcQzc)YG(#emsDWehj8h5Uf)MOXH*@5FS)UATL~t;Ifcyv*QV zelMBwTdc`Ir>j!3yFKKr=g+m}n`fwmsKf;1m;6IN2CybWrq?G$*JrPvh8MSG?Ox@v z<9zkJ|9z>f#%1M4zCAu4_ohm~$xD&2_n8faDN7DR8lsB;b+P+C4gBlw$>|WtXy<8|zcVPlx(U=a zO@}{#k|!N;cbXgVx}yJ_=EWswZ$kYRGcLyDx}O}>JWhUnUy->y-F`~Mr;3|T2ICd$ z;3P=Zaz0^wk* zFVQ4`BkgdQ_I3+19W%`Nex3GMJLlX_<01Z{L);|WUCGs&3<1Y<&Dr{B0LaWg9+W2w ze{P9sEZ)T9SbOj&rndsv_Tz9u4JM@Blgq3PxY0MR@8}WP~U(o(OwMW zUE8Or+tqbR0oiT|g;qvMz8&^pGh|@`@0@h9Ls1B0R_d+rgUmE<0X^)g%~P3JSjLpg z(~CDMgyzw;m1Bf8!X+y^jc5Yxrn{G*w};Gh^91(hWoKf|3(P4=N-^8Z+mr-}+g37e z_9Jz955YWJ1@8q$MObsEJLr3y2ETqY5A`U9&bZ?D5=bvcMl#t(WRD9(s}x(H>YE9t>R>z+)$Sm1n;!t=^N z*#979Us%OfA`5P~3~ zVB&TM@wXTfVcvhvNiHvST%SQW4`GvgvPsYGTc$ucibaBmqxXDyY?W9ea^ zHe<%2-OS#sBOYz-bff|=@9+Ow8a-j}=a)!nqsW2~_^v-R)0VvTZk2d{Ix{2GnlQr~ zFB7YrGF)hmz*q8lb9u1$5qP28=6paQ(?n4rZ&=tiS=-)XTJZ~NuJRL6@`bnkNQX4{j zf0Qk4;4tC%kXQ>osWdz>z-vqX38K;aR7}Dd)PY7iXABgI*80SpQt2DQ+XtQSm!H4! zBB6X2TIFm=puIygJt1cxW^qWg!J|CTvf=tVKh?Y}_~DO(ZF$cpu#(58$^ITOKLdT} zMeMdYVYt1(f#&s6+u`|_g<@rC!hrwZ zMQD2j^(82LHCTL$nFbM|4KRUQGh~To&ZCtt?hRXrO*8ef@U2^P)yqUIxrUcv7nskI z-A)3wks|;EmWlx){%ebd6Y9V1@9tOVY4;U+%BOe!UQ4?$rC@ABh`#I-z!>SPF8m#4FIWR=G28YSqFf8sizl^anqXk(XF6mIRouwSa-|O@ zsN?{sd^JmkOY-i;4Y}Y|4GrwG|5#J_Up`1Jg$KbN+{&k|aa0-bdJtx74{QZSU-?dy zFtBFemkg~vc9F)Ef!jNF4nSg((?EWJGH?mnSEIRpQApqZyAA@m@FOH*#gB-F z8V)MW)|eSvPE{wwqK}}#A8L{c+1S7_0wMC+TFEN6VhliR5?zd874Sdd5hDKu=4ts6 zUFyLnF(YI@ekj%{*dss5Ny9MiRC54F-KBDmH@P_s;7LanX5}-y7e8Vt>BxDg@lN5F z9yZ1!IRR`WoufXpqMLtkbKI-R55Mm?iV7`03vOag)orr06`ekb2Wff4+#%in80Tz4 zrVpY#Y;!n*z|G5!9d~$KdBzSJt&HMULL)Ue%FRmr0jlsLgoV%>)WjGHJLo5zhIU)q z7MOBLF@13GBvvWN_C@Cx@qM`da$)SdGVf;$H3`riavh{gkdG=WyY;4RmBSaWPp#vo zV&Hh)`_OH1t%PS)Ujp3M3o9O<@gctqxtoz-!yOT=0@|6PIwP%@PU~ZfWpXD5o1W$_P;x%c2@IVY;x(=8K`M7QP#kJS!9 z>LmZ#3IO%K1fB8?)MDPyMtlI)h%_P64e*%xrhw|tiv?EPJFg|ddGRyMu1ipc zh8iHOSfDwl)J*&|Q@7~XY0 z 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

H*_-A=FSc{k11IzP*4;OykH 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