From 0f796899613c30985b215dae252247920bed72de Mon Sep 17 00:00:00 2001 From: ianchen0119 Date: Sat, 7 Aug 2021 22:19:30 +0800 Subject: [PATCH 1/3] add ch9 --- 09-MemoryAllocator/Makefile | 52 +++++ 09-MemoryAllocator/README.md | 75 +++++++ 09-MemoryAllocator/gdbinit | 4 + 09-MemoryAllocator/include/lib.h | 28 +++ 09-MemoryAllocator/include/os.h | 42 ++++ 09-MemoryAllocator/include/riscv.h | 162 +++++++++++++++ 09-MemoryAllocator/include/string.h | 10 + 09-MemoryAllocator/include/sys.h | 8 + 09-MemoryAllocator/include/task.h | 16 ++ 09-MemoryAllocator/include/timer.h | 12 ++ 09-MemoryAllocator/include/types.h | 13 ++ 09-MemoryAllocator/include/virtio.h | 129 ++++++++++++ 09-MemoryAllocator/os.ld | 49 +++++ 09-MemoryAllocator/src/alloc.c | 208 +++++++++++++++++++ 09-MemoryAllocator/src/lib.c | 247 ++++++++++++++++++++++ 09-MemoryAllocator/src/lock.c | 32 +++ 09-MemoryAllocator/src/mem.s | 30 +++ 09-MemoryAllocator/src/os.c | 51 +++++ 09-MemoryAllocator/src/plic.c | 48 +++++ 09-MemoryAllocator/src/start.s | 26 +++ 09-MemoryAllocator/src/string.c | 40 ++++ 09-MemoryAllocator/src/sys.s | 152 ++++++++++++++ 09-MemoryAllocator/src/task.c | 32 +++ 09-MemoryAllocator/src/timer.c | 38 ++++ 09-MemoryAllocator/src/trap.c | 91 ++++++++ 09-MemoryAllocator/src/user.c | 60 ++++++ 09-MemoryAllocator/src/virtio.c | 308 ++++++++++++++++++++++++++++ 27 files changed, 1963 insertions(+) create mode 100644 09-MemoryAllocator/Makefile create mode 100644 09-MemoryAllocator/README.md create mode 100644 09-MemoryAllocator/gdbinit create mode 100644 09-MemoryAllocator/include/lib.h create mode 100644 09-MemoryAllocator/include/os.h create mode 100644 09-MemoryAllocator/include/riscv.h create mode 100644 09-MemoryAllocator/include/string.h create mode 100644 09-MemoryAllocator/include/sys.h create mode 100644 09-MemoryAllocator/include/task.h create mode 100644 09-MemoryAllocator/include/timer.h create mode 100644 09-MemoryAllocator/include/types.h create mode 100644 09-MemoryAllocator/include/virtio.h create mode 100644 09-MemoryAllocator/os.ld create mode 100644 09-MemoryAllocator/src/alloc.c create mode 100644 09-MemoryAllocator/src/lib.c create mode 100644 09-MemoryAllocator/src/lock.c create mode 100644 09-MemoryAllocator/src/mem.s create mode 100644 09-MemoryAllocator/src/os.c create mode 100644 09-MemoryAllocator/src/plic.c create mode 100644 09-MemoryAllocator/src/start.s create mode 100644 09-MemoryAllocator/src/string.c create mode 100644 09-MemoryAllocator/src/sys.s create mode 100644 09-MemoryAllocator/src/task.c create mode 100644 09-MemoryAllocator/src/timer.c create mode 100644 09-MemoryAllocator/src/trap.c create mode 100644 09-MemoryAllocator/src/user.c create mode 100644 09-MemoryAllocator/src/virtio.c diff --git a/09-MemoryAllocator/Makefile b/09-MemoryAllocator/Makefile new file mode 100644 index 0000000..8d1b0f7 --- /dev/null +++ b/09-MemoryAllocator/Makefile @@ -0,0 +1,52 @@ +CC = riscv64-unknown-elf-gcc +CFLAGS = -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w +GDB = riscv64-unknown-elf-gdb +SOURCE = src/ + +OBJ = \ +$(SOURCE)start.s \ +$(SOURCE)sys.s \ +$(SOURCE)mem.s \ +$(SOURCE)lib.c \ +$(SOURCE)timer.c \ +$(SOURCE)task.c \ +$(SOURCE)os.c \ +$(SOURCE)user.c \ +$(SOURCE)trap.c \ +$(SOURCE)lock.c \ +$(SOURCE)plic.c \ +$(SOURCE)virtio.c \ +$(SOURCE)string.c \ +$(SOURCE)alloc.c + +QEMU = qemu-system-riscv32 +QFLAGS = -nographic -smp 4 -machine virt -bios none +QFLAGS += -drive if=none,format=raw,file=hdd.dsk,id=x0 +QFLAGS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 + +OBJDUMP = riscv64-unknown-elf-objdump + +all: clean os.elf hdd.dsk qemu + +test: clean os.elf qemu + +os.elf: $(OBJ) + $(CC) $(CFLAGS) -T os.ld -o os.elf $^ + +qemu: $(TARGET) hdd.dsk + @qemu-system-riscv32 -M ? | grep virt >/dev/null || exit + @echo "Press Ctrl-A and then X to exit QEMU" + $(QEMU) $(QFLAGS) -kernel os.elf + +clean: + rm -f *.elf *.img + +hdd.dsk: + dd if=/dev/urandom of=hdd.dsk bs=1M count=32 + +.PHONY : debug +debug: clean os.elf hdd.dsk + @echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU" + @echo "-------------------------------------------------------" + @${QEMU} ${QFLAGS} -kernel os.elf -s -S & + @${GDB} os.elf -q -x ./gdbinit \ No newline at end of file diff --git a/09-MemoryAllocator/README.md b/09-MemoryAllocator/README.md new file mode 100644 index 0000000..fe209cb --- /dev/null +++ b/09-MemoryAllocator/README.md @@ -0,0 +1,75 @@ +# 09-MemoryAllocator + +## Build & Run + +```sh +IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/09-MemoryAllocator (master) +$ make all +rm -f *.elf *.img +riscv64-unknown-elf-gcc -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -T os.ld -o os.elf src/start.s src/sys.s src/mem.s src/lib.c src/timer.c src/task.c src/os.c src/user.c src/trap.c src/lock.c src/plic.c src/virtio.c src/string.c src/alloc.c +Press Ctrl-A and then X to exit QEMU +qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf +HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 32742 +TEXT: 0x80000000 -> 0x8000ac5c +RODATA: 0x8000ac5c -> 0x8000b083 +DATA: 0x8000c000 -> 0x8000c004 +BSS: 0x8000d000 -> 0x8001100c +HEAP: 0x8001a000 -> 0x88000000 +OS start +Disk init work is success! +buffer init... +block read... +Virtio IRQ +000000fd +000000af +000000f8 +000000ab +00000088 +00000042 +000000cc +00000017 +00000022 +0000008e + +OS: Activate next task +Task0: Created! +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +QEMU: Terminated +``` + +## Debug mode + +```sh +make debug +riscv64-unknown-elf-gcc -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -T os.ld -o os.elf start.s sys.s lib.c timer.c task.c os.c user.c trap.c lock.c +Press Ctrl-C and then input 'quit' to exit GDB and QEMU +------------------------------------------------------- +Reading symbols from os.elf... +Breakpoint 1 at 0x80000000: file start.s, line 7. +0x00001000 in ?? () +=> 0x00001000: 97 02 00 00 auipc t0,0x0 + +Thread 1 hit Breakpoint 1, _start () at start.s:7 +7 csrr t0, mhartid # read current hart id +=> 0x80000000 <_start+0>: f3 22 40 f1 csrr t0,mhartid +(gdb) +``` + +### set the breakpoint + +You can set the breakpoint in any c file: + +```sh +(gdb) b trap.c:27 +Breakpoint 2 at 0x80008f78: file trap.c, line 27. +(gdb) +``` + +As the example above, when process running on trap.c, line 27 (Timer Interrupt). +The process will be suspended automatically until you press the key `c` (continue) or `s` (step). diff --git a/09-MemoryAllocator/gdbinit b/09-MemoryAllocator/gdbinit new file mode 100644 index 0000000..7ca6884 --- /dev/null +++ b/09-MemoryAllocator/gdbinit @@ -0,0 +1,4 @@ +set disassemble-next-line on +b _start +target remote : 1234 +c \ No newline at end of file diff --git a/09-MemoryAllocator/include/lib.h b/09-MemoryAllocator/include/lib.h new file mode 100644 index 0000000..be9b9fd --- /dev/null +++ b/09-MemoryAllocator/include/lib.h @@ -0,0 +1,28 @@ +#ifndef __LIB_H__ +#define __LIB_H__ + +#include "riscv.h" +#include +#include + +#define lib_error(...) \ + { \ + lib_printf(__VA_ARGS__); \ + while (1) \ + { \ + } \ + } \ + } + +extern char *lib_gets(char *); +extern void uart_init(); +extern void lib_isr(void); +extern int lib_getc(void); +extern void lib_delay(volatile int count); +extern int lib_putc(char ch); +extern void lib_puts(char *s); +extern int lib_printf(const char *s, ...); +extern int lib_vprintf(const char *s, va_list vl); +extern int lib_vsnprintf(char *out, size_t n, const char *s, va_list vl); + +#endif diff --git a/09-MemoryAllocator/include/os.h b/09-MemoryAllocator/include/os.h new file mode 100644 index 0000000..8caa046 --- /dev/null +++ b/09-MemoryAllocator/include/os.h @@ -0,0 +1,42 @@ +#ifndef __OS_H__ +#define __OS_H__ + +#include "riscv.h" +#include "lib.h" +#include "task.h" +#include "timer.h" +#include "string.h" + +extern void panic(char *); +extern void user_init(); +extern void os_kernel(); +extern int os_main(void); + +// PLIC +extern void plic_init(); +extern int plic_claim(); +extern void plic_complete(int); + +// lock +extern void basic_lock(); +extern void basic_unlock(); + +typedef struct lock +{ + volatile int locked; +} lock_t; + +extern int atomic_swap(lock_t *); + +extern void lock_init(lock_t *lock); + +extern void lock_acquire(lock_t *lock); + +extern void lock_free(lock_t *lock); + +// memory allocator + +extern void *page_alloc(int npages); +extern void page_free(void *p); + +#endif diff --git a/09-MemoryAllocator/include/riscv.h b/09-MemoryAllocator/include/riscv.h new file mode 100644 index 0000000..1bfe445 --- /dev/null +++ b/09-MemoryAllocator/include/riscv.h @@ -0,0 +1,162 @@ +#ifndef __RISCV_H__ +#define __RISCV_H__ + +#include + +#define reg_t uint32_t // RISCV32: register is 32bits +// define reg_t as uint64_t // RISCV64: register is 64bits + +#define PGSIZE 4096 // bytes per page + +// ref: https://www.activexperts.com/serial-port-component/tutorials/uart/ +#define UART 0x10000000L +#define UART_THR (volatile uint8_t *)(UART + 0x00) // THR:transmitter holding register +#define UART_RHR (volatile uint8_t *)(UART + 0x00) // RHR:Receive holding register +#define UART_DLL (volatile uint8_t *)(UART + 0x00) // LSB of Divisor Latch (write mode) +#define UART_DLM (volatile uint8_t *)(UART + 0x01) // MSB of Divisor Latch (write mode) +#define UART_IER (volatile uint8_t *)(UART + 0x01) // Interrupt Enable Register +#define UART_LCR (volatile uint8_t *)(UART + 0x03) // Line Control Register +#define UART_LSR (volatile uint8_t *)(UART + 0x05) // LSR:line status register +#define UART_LSR_EMPTY_MASK 0x40 // LSR Bit 6: Transmitter empty; both the THR and LSR are empty + +#define UART_REGR(reg) (*(reg)) +#define UART_REGW(reg, v) ((*reg) = (v)) + +// ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h +// enum { +// UART0_IRQ = 10, +// RTC_IRQ = 11, +// VIRTIO_IRQ = 1, /* 1 to 8 */ +// VIRTIO_COUNT = 8, +// PCIE_IRQ = 0x20, /* 32 to 35 */ +// VIRTIO_NDEV = 0x35 /* Arbitrary maximum number of interrupts */ +// }; +#define UART0_IRQ 10 +#define VIRTIO_IRQ 1 + +// Saved registers for kernel context switches. +struct context +{ + reg_t ra; + reg_t sp; + + // callee-saved + reg_t s0; + reg_t s1; + reg_t s2; + reg_t s3; + reg_t s4; + reg_t s5; + reg_t s6; + reg_t s7; + reg_t s8; + reg_t s9; + reg_t s10; + reg_t s11; +}; + +// ref: https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/riscv.h +// +// local interrupt controller, which contains the timer. +// ================== Timer Interrput ==================== + +#define NCPU 8 // maximum number of CPUs +#define CLINT 0x2000000 +#define CLINT_MTIMECMP(hartid) (CLINT + 0x4000 + 4 * (hartid)) +#define CLINT_MTIME (CLINT + 0xBFF8) // cycles since boot. + +static inline reg_t r_tp() +{ + reg_t x; + asm volatile("mv %0, tp" + : "=r"(x)); + return x; +} + +// which hart (core) is this? + +static inline reg_t r_mhartid() +{ + reg_t x; + asm volatile("csrr %0, mhartid" + : "=r"(x)); + return x; +} + +// Machine Status Register, mstatus +#define MSTATUS_MPP_MASK (3L << 11) // previous mode. +#define MSTATUS_MPP_M (3L << 11) +#define MSTATUS_MPP_S (1L << 11) +#define MSTATUS_MPP_U (0L << 11) +#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable. + +static inline reg_t r_mstatus() +{ + reg_t x; + asm volatile("csrr %0, mstatus" + : "=r"(x)); + return x; +} + +static inline void w_mstatus(reg_t x) +{ + asm volatile("csrw mstatus, %0" + : + : "r"(x)); +} + +// machine exception program counter, holds the +// instruction address to which a return from +// exception will go. +static inline void w_mepc(reg_t x) +{ + asm volatile("csrw mepc, %0" + : + : "r"(x)); +} + +static inline reg_t r_mepc() +{ + reg_t x; + asm volatile("csrr %0, mepc" + : "=r"(x)); + return x; +} + +// Machine Scratch register, for early trap handler +static inline void w_mscratch(reg_t x) +{ + asm volatile("csrw mscratch, %0" + : + : "r"(x)); +} + +// Machine-mode interrupt vector +static inline void w_mtvec(reg_t x) +{ + asm volatile("csrw mtvec, %0" + : + : "r"(x)); +} + +// Machine-mode Interrupt Enable +#define MIE_MEIE (1L << 11) // external +#define MIE_MTIE (1L << 7) // timer +#define MIE_MSIE (1L << 3) // software + +static inline reg_t r_mie() +{ + reg_t x; + asm volatile("csrr %0, mie" + : "=r"(x)); + return x; +} + +static inline void w_mie(reg_t x) +{ + asm volatile("csrw mie, %0" + : + : "r"(x)); +} + +#endif diff --git a/09-MemoryAllocator/include/string.h b/09-MemoryAllocator/include/string.h new file mode 100644 index 0000000..71bc3d2 --- /dev/null +++ b/09-MemoryAllocator/include/string.h @@ -0,0 +1,10 @@ +#ifndef __STRING_H__ +#define __STRING_H__ + +void *memset(void *, int, unsigned int); +void * +memcpy(void *dst, const void *src, unsigned int n); +void * +memmove(void *dst, const void *src, unsigned int n); + +#endif \ No newline at end of file diff --git a/09-MemoryAllocator/include/sys.h b/09-MemoryAllocator/include/sys.h new file mode 100644 index 0000000..4355ab3 --- /dev/null +++ b/09-MemoryAllocator/include/sys.h @@ -0,0 +1,8 @@ +#ifndef __SYS_H__ +#define __SYS_H__ + +#include "riscv.h" +extern void sys_timer(); +extern void sys_switch(struct context *ctx_old, struct context *ctx_new); + +#endif diff --git a/09-MemoryAllocator/include/task.h b/09-MemoryAllocator/include/task.h new file mode 100644 index 0000000..1ac7e39 --- /dev/null +++ b/09-MemoryAllocator/include/task.h @@ -0,0 +1,16 @@ +#ifndef __TASK_H__ +#define __TASK_H__ + +#include "riscv.h" +#include "sys.h" + +#define MAX_TASK 10 +#define STACK_SIZE 1024 + +extern int taskTop; + +extern int task_create(void (*task)(void)); +extern void task_go(int i); +extern void task_os(); + +#endif diff --git a/09-MemoryAllocator/include/timer.h b/09-MemoryAllocator/include/timer.h new file mode 100644 index 0000000..b971c37 --- /dev/null +++ b/09-MemoryAllocator/include/timer.h @@ -0,0 +1,12 @@ +#ifndef __TIMER_H__ +#define __TIMER_H__ + +#include "riscv.h" +#include "sys.h" +#include "lib.h" +#include "task.h" + +extern void timer_handler(); +extern void timer_init(); + +#endif diff --git a/09-MemoryAllocator/include/types.h b/09-MemoryAllocator/include/types.h new file mode 100644 index 0000000..15c7cbc --- /dev/null +++ b/09-MemoryAllocator/include/types.h @@ -0,0 +1,13 @@ +#ifndef __TYPES_H__ +#define __TYPES_H__ + +typedef unsigned int uint; +typedef unsigned short ushort; +typedef unsigned char uchar; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; + +#endif \ No newline at end of file diff --git a/09-MemoryAllocator/include/virtio.h b/09-MemoryAllocator/include/virtio.h new file mode 100644 index 0000000..ff34616 --- /dev/null +++ b/09-MemoryAllocator/include/virtio.h @@ -0,0 +1,129 @@ +#include "types.h" +/* + * References: + * 1. https://github.com/ianchen0119/xv6-riscv/blob/riscv/kernel/virtio.h + * 2. https://github.com/sgmarz/osblog/blob/master/risc_v/src/virtio.rs + */ +#define VIRTIO_MMIO_BASE 0x10001000 +/* OFFSET */ +#define VIRTIO_MMIO_MAGIC_VALUE 0x000 // Magic value must be 0x74726976 +#define VIRTIO_MMIO_VERSION 0x004 // Version: 1 (Legacy) + +/* + * Device ID: + * 1 (Network Device) + * 2 (Block Device) + * 4 (Random number generator Device) + * 16 (GPU Device) + * 18 (Input Device) + */ + +#define VIRTIO_MMIO_DEVICE_ID 0x008 +#define VIRTIO_MMIO_VENDOR_ID 0x00c // 0x554d4551 +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 +#define VIRTIO_MMIO_GUEST_PAGE_SIZE 0x028 // page size for PFN, write-only +#define VIRTIO_MMIO_QUEUE_SEL 0x030 // select queue, write-only +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 // max size of current queue, read-only +#define VIRTIO_MMIO_QUEUE_NUM 0x038 // size of current queue, write-only +#define VIRTIO_MMIO_QUEUE_ALIGN 0x03c // used ring alignment, write-only +#define VIRTIO_MMIO_QUEUE_PFN 0x040 // physical page number for queue, read/write +#define VIRTIO_MMIO_QUEUE_READY 0x044 // ready bit +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 // write-only +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 // read-only +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 // write-only +#define VIRTIO_MMIO_STATUS 0x070 // read/write +#define VIRTIO_MMIO_QueueDescLow 0x080 +#define VIRTIO_MMIO_QueueDescHigh 0x084 +#define VIRTIO_MMIO_QueueAvailLow 0x090 +#define VIRTIO_MMIO_QueueAvailHigh 0x094 +#define VIRTIO_MMIO_QueueUsedLow 0x0a0 +#define VIRTIO_MMIO_QueueUsedHigh 0x0a4 + +// status register bits, from qemu virtio_config.h +#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 +#define VIRTIO_CONFIG_S_DRIVER 2 +#define VIRTIO_CONFIG_S_DRIVER_OK 4 +#define VIRTIO_CONFIG_S_FEATURES_OK 8 + +// device feature bits +#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */ +#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */ +#define VIRTIO_BLK_F_CONFIG_WCE 11 /* Writeback mode available in config */ +#define VIRTIO_BLK_F_MQ 12 /* support more than one vq */ +#define VIRTIO_F_ANY_LAYOUT 27 +#define VIRTIO_RING_F_INDIRECT_DESC 28 +#define VIRTIO_RING_F_EVENT_IDX 29 + +// this many virtio descriptors. +#define NUM 8 + +// 描述符包含這些訊息: 地址,地址長度,某些標誌和其他信息。使用此描述符,我們可以將設備指向 RAM 中任何緩衝區的內存地址。 +typedef struct virtq_desc +{ + /* + * addr: 我們可以在 64-bit 內存地址內的任何位置告訴設備存儲位置。 + * len: 讓 Device 知道有多少內存可用。 + * flags: 用於控制 descriptor 。 + * next: 告訴 Device 下一個描述符的 Index 。如果指定了 VIRTQ_DESC_F_NEXT, Device 僅讀取該字段。否則無效。 + */ + uint64 addr; + uint32 len; + uint16 flags; + uint16 next; +} virtq_desc_t; + +#define VRING_DESC_F_NEXT 1 // chained with another descriptor +#define VRING_DESC_F_WRITE 2 // device writes (vs read) +#define VRING_DESC_F_INDIRECT 4 // buffer contains a list of buffer descriptors + +/* + * 用來存放 descriptor 的索引,當 Device 收到通知時,它會檢查 AvailableRing 確認需要讀取哪些 Descriptor 。 + * 注意: Descriptor 和 AvailableRing 都存儲在 RAM 中。 + */ + +typedef struct virtq_avail +{ + uint16 flags; // always zero + uint16 idx; // driver will write ring[idx] next + uint16 ring[NUM]; // descriptor numbers of chain heads +} virtq_avail_t; + +/* + * 當內部的 Index 與 UsedRing 的 Index 相等,代表所有資料都已經被讀取,這個 Device 是唯一需要被寫進 Index 的。 + */ + +typedef struct virtq_used_elem +{ + uint32 id; // index of start of completed descriptor chain + uint32 len; +} virtq_used_elem_t; + +/* + * Device 可以使用 UsedRing 向 OS 發送訊息。 + * Device 通常使用它來告知 OS 它已完成先前通知的請求。 + * AvailableRing 與 UsedRing 非常相似,差異在於: OS 需要查看 UsedRing 得知哪個 Descriptor 已經被服務。 + */ + +typedef struct virtq_used +{ + uint16 flags; // always zero + uint16 idx; // device increments when it adds a ring[] entry + struct virtq_used_elem ring[NUM]; +} virtq_used_t; + +// these are specific to virtio block devices, e.g. disks, +// described in Section 5.2 of the spec. + +#define VIRTIO_BLK_T_IN 0 // read the disk +#define VIRTIO_BLK_T_OUT 1 // write the disk + +// the format of the first descriptor in a disk request. +// to be followed by two more descriptors containing +// the block, and a one-byte status. +typedef struct virtio_blk_req +{ + uint32 type; // VIRTIO_BLK_T_IN or ..._OUT + uint32 reserved; // 將 Header 擴充到 16-byte ,並將 64-bit sector 移到正確的位置。 + uint64 sector; +} virtio_blk_req_t; \ No newline at end of file diff --git a/09-MemoryAllocator/os.ld b/09-MemoryAllocator/os.ld new file mode 100644 index 0000000..32be052 --- /dev/null +++ b/09-MemoryAllocator/os.ld @@ -0,0 +1,49 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY( _start ) + +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} + +PHDRS +{ + text PT_LOAD; + data PT_LOAD; + bss PT_LOAD; +} + +SECTIONS +{ + .text : { + PROVIDE(_text_start = .); + *(.text.init) *(.text .text.*) + PROVIDE(_text_end = .); + } >ram AT>ram :text + + .rodata : { + PROVIDE(_rodata_start = .); + *(.rodata .rodata.*) + PROVIDE(_rodata_end = .); + } >ram AT>ram :text + + .data : { + . = ALIGN(4096); + PROVIDE(_data_start = .); + *(.sdata .sdata.*) *(.data .data.*) + PROVIDE(_data_end = .); + } >ram AT>ram :data + + .bss :{ + PROVIDE(_bss_start = .); + *(.sbss .sbss.*) *(.bss .bss.*) + PROVIDE(_bss_end = .); + } >ram AT>ram :bss + + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); +} diff --git a/09-MemoryAllocator/src/alloc.c b/09-MemoryAllocator/src/alloc.c new file mode 100644 index 0000000..741c4eb --- /dev/null +++ b/09-MemoryAllocator/src/alloc.c @@ -0,0 +1,208 @@ +#include "os.h" + +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; + +/* + * _alloc_start points to the actual start address of heap pool + * _alloc_end points to the actual end address of heap pool + * _num_pages holds the actual max number of pages we can allocate. + */ + +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 4096 +#define PAGE_ORDER 12 + +#define PAGE_TAKEN (uint8_t)(1 << 0) +#define PAGE_LAST (uint8_t)(1 << 1) + +/* + * Page Descriptor + * flags: + * - 00: This means this page hasn't been allocated + * - 01: This means this page was allocated + * - 10: This means this page hasn't been allocated and is the last page of the memory block allocated + * - 11: This means this page was allocated and is the last page of the memory block allocated + */ +struct Page +{ + uint8_t flags; +}; + +static inline void _clear(struct Page *page) +{ + page->flags = 0; +} + +static inline int _is_free(struct Page *page) +{ + if (page->flags & PAGE_TAKEN) + { + return 0; + } + else + { + return 1; + } +} + +static inline void _set_flag(struct Page *page, uint8_t flags) +{ + page->flags |= flags; +} + +static inline int _is_last(struct Page *page) +{ + if (page->flags & PAGE_LAST) + { + return 1; + } + else + { + return 0; + } +} + +/* + * align the address to the border of page(4K) + */ +static inline uint32_t _align_page(uint32_t address) +{ + uint32_t order = (1 << PAGE_ORDER) - 1; + return (address + order) & (~order); +} + +void page_init() +{ + /* + * We reserved 8 Page (8 x 4096) to hold the Page structures. + * It should be enough to manage at most 128 MB (8 x 4096 x 4096) + */ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + lib_printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) + { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + lib_printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + lib_printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + lib_printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + lib_printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + lib_printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} + +/* + * Allocate a memory block which is composed of contiguous physical pages + * - npages: the number of PAGE_SIZE pages to allocate + */ +void *page_alloc(int npages) +{ + /* Note we are searching the page descriptor bitmaps. */ + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) + { + if (_is_free(page_i)) + { + found = 1; + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) + { + if (!_is_free(page_j)) + { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) + { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) + { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} + +/* + * Free the memory block + * - p: start address of the memory block + */ +void page_free(void *p) +{ + /* + * Assert (TBD) if p is invalid + */ + if (!p || (uint32_t)p >= _alloc_end) + { + return; + } + /* get the first page descriptor of this memory block */ + struct Page *page = (struct Page *)HEAP_START; + page += ((uint32_t)p - _alloc_start) / PAGE_SIZE; + /* loop and clear all the page descriptors of the memory block */ + while (!_is_free(page)) + { + if (_is_last(page)) + { + _clear(page); + break; + } + else + { + _clear(page); + page++; + ; + } + } +} + +void page_test() +{ + void *p = page_alloc(2); + lib_printf("p = 0x%x\n", p); + page_free(p); + + void *p2 = page_alloc(7); + lib_printf("p2 = 0x%x\n", p2); + page_free(p2); + + void *p3 = page_alloc(4); + lib_printf("p3 = 0x%x\n", p3); +} \ No newline at end of file diff --git a/09-MemoryAllocator/src/lib.c b/09-MemoryAllocator/src/lib.c new file mode 100644 index 0000000..77b47da --- /dev/null +++ b/09-MemoryAllocator/src/lib.c @@ -0,0 +1,247 @@ +#include "lib.h" + +#define LSR_RX_READY (1 << 0) +#define EOF 0 + +void uart_init() +{ + /* disable interrupts */ + UART_REGW(UART_IER, 0x00); + + /* Baud rate setting */ + uint8_t lcr = UART_REGR(UART_LCR); + UART_REGW(UART_LCR, lcr | (1 << 7)); + UART_REGW(UART_DLL, 0x03); + UART_REGW(UART_DLM, 0x00); + + lcr = 0; + UART_REGW(UART_LCR, lcr | (3 << 0)); + + uint8_t ier = UART_REGR(UART_IER); + UART_REGW(UART_IER, ier | (1 << 0)); +} + +char *lib_gets(char *s) +{ + int ch; + char *p = s; + + while ((ch = lib_getc()) != '\n' && ch != EOF) + { + if (ch == -1) + { + continue; + } + *s = (char)ch; + s++; + } + + *s = '\0'; + return p; +} + +int lib_getc(void) +{ + if (*UART_LSR & LSR_RX_READY) + { + return *UART_RHR == '\r' ? '\n' : *UART_RHR; + } + else + { + return -1; + } +} + +void lib_isr(void) +{ + for (;;) + { + int c = lib_getc(); + if (c == -1) + { + break; + } + else + { + lib_putc((char)c); + lib_putc('\n'); + } + } +} + +void lib_delay(volatile int count) +{ + count *= 50000; + while (count--) + ; +} + +int lib_putc(char ch) +{ + while ((*UART_LSR & UART_LSR_EMPTY_MASK) == 0) + ; + return *UART_THR = ch; +} + +void lib_puts(char *s) +{ + while (*s) + lib_putc(*s++); +} + +int lib_vsnprintf(char *out, size_t n, const char *s, va_list vl) +{ + int format = 0; + int longarg = 0; + size_t pos = 0; + for (; *s; s++) + { + if (format) + { + switch (*s) + { + case 'l': + { + longarg = 1; + break; + } + case 'p': + { + longarg = 1; + if (out && pos < n) + { + out[pos] = '0'; + } + pos++; + if (out && pos < n) + { + out[pos] = 'x'; + } + pos++; + } + case 'x': + { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + int hexdigits = 2 * (longarg ? sizeof(long) : sizeof(int)) - 1; + for (int i = hexdigits; i >= 0; i--) + { + int d = (num >> (4 * i)) & 0xF; + if (out && pos < n) + { + out[pos] = (d < 10 ? '0' + d : 'a' + d - 10); + } + pos++; + } + longarg = 0; + format = 0; + break; + } + case 'd': + { + long num = longarg ? va_arg(vl, long) : va_arg(vl, int); + if (num < 0) + { + num = -num; + if (out && pos < n) + { + out[pos] = '-'; + } + pos++; + } + long digits = 1; + for (long nn = num; nn /= 10; digits++) + ; + for (int i = digits - 1; i >= 0; i--) + { + if (out && pos + i < n) + { + out[pos + i] = '0' + (num % 10); + } + num /= 10; + } + pos += digits; + longarg = 0; + format = 0; + break; + } + case 's': + { + const char *s2 = va_arg(vl, const char *); + while (*s2) + { + if (out && pos < n) + { + out[pos] = *s2; + } + pos++; + s2++; + } + longarg = 0; + format = 0; + break; + } + case 'c': + { + if (out && pos < n) + { + out[pos] = (char)va_arg(vl, int); + } + pos++; + longarg = 0; + format = 0; + break; + } + default: + break; + } + } + else if (*s == '%') + { + format = 1; + } + else + { + if (out && pos < n) + { + out[pos] = *s; + } + pos++; + } + } + if (out && pos < n) + { + out[pos] = 0; + } + else if (out && n) + { + out[n - 1] = 0; + } + return pos; +} + +static char out_buf[1000]; // buffer for lib_vprintf() + +int lib_vprintf(const char *s, va_list vl) +{ + int res = lib_vsnprintf(NULL, -1, s, vl); + if (res + 1 >= sizeof(out_buf)) + { + lib_puts("error: lib_vprintf() output string size overflow\n"); + while (1) + { + } + } + lib_vsnprintf(out_buf, res + 1, s, vl); + lib_puts(out_buf); + return res; +} + +int lib_printf(const char *s, ...) +{ + int res = 0; + va_list vl; + va_start(vl, s); + res = lib_vprintf(s, vl); + va_end(vl); + return res; +} diff --git a/09-MemoryAllocator/src/lock.c b/09-MemoryAllocator/src/lock.c new file mode 100644 index 0000000..309782a --- /dev/null +++ b/09-MemoryAllocator/src/lock.c @@ -0,0 +1,32 @@ +#include "os.h" + +void lock_init(lock_t *lock) +{ + lock->locked = 0; +} + +void lock_acquire(lock_t *lock) +{ + for (;;) + { + if (!atomic_swap(lock)) + { + break; + } + } +} + +void lock_free(lock_t *lock) +{ + lock->locked = 0; +} + +void basic_lock() +{ + w_mstatus(r_mstatus() & ~MSTATUS_MIE); +} + +void basic_unlock() +{ + w_mstatus(r_mstatus() | MSTATUS_MIE); +} diff --git a/09-MemoryAllocator/src/mem.s b/09-MemoryAllocator/src/mem.s new file mode 100644 index 0000000..dede6f3 --- /dev/null +++ b/09-MemoryAllocator/src/mem.s @@ -0,0 +1,30 @@ +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end \ No newline at end of file diff --git a/09-MemoryAllocator/src/os.c b/09-MemoryAllocator/src/os.c new file mode 100644 index 0000000..0995e4f --- /dev/null +++ b/09-MemoryAllocator/src/os.c @@ -0,0 +1,51 @@ +#include "os.h" + +extern void page_init(void); + +extern void trap_init(void); + +void panic(char *s) +{ + lib_puts(s); + for (;;) + { + } +} + +void os_kernel() +{ + task_os(); +} + +void disk_read() +{ + virtio_tester(0); +} + +void os_start() +{ + uart_init(); + page_init(); + lib_puts("OS start\n"); + user_init(); + trap_init(); + plic_init(); + virtio_disk_init(); + timer_init(); // start timer interrupt ... +} + +int os_main(void) +{ + os_start(); + disk_read(); + int current_task = 0; + while (1) + { + lib_puts("OS: Activate next task\n"); + task_go(current_task); + lib_puts("OS: Back to OS\n"); + current_task = (current_task + 1) % taskTop; // Round Robin Scheduling + lib_puts("\n"); + } + return 0; +} diff --git a/09-MemoryAllocator/src/plic.c b/09-MemoryAllocator/src/plic.c new file mode 100644 index 0000000..32e0c8a --- /dev/null +++ b/09-MemoryAllocator/src/plic.c @@ -0,0 +1,48 @@ +#include "os.h" + +// ref: https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h +// Intro: https://github.com/ianchen0119/AwesomeCS/wiki/2-5-RISC-V::%E4%B8%AD%E6%96%B7%E8%88%87%E7%95%B0%E5%B8%B8%E8%99%95%E7%90%86----PLIC-%E4%BB%8B%E7%B4%B9 +#define PLIC_BASE 0x0c000000L +#define PLIC_PRIORITY(id) (PLIC_BASE + (id)*4) +#define PLIC_PENDING(id) (PLIC_BASE + 0x1000 + ((id) / 32)) +#define PLIC_MENABLE(hart) (PLIC_BASE + 0x2000 + (hart)*0x80) +#define PLIC_MTHRESHOLD(hart) (PLIC_BASE + 0x200000 + (hart)*0x1000) +#define PLIC_MCLAIM(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) +#define PLIC_MCOMPLETE(hart) (PLIC_BASE + 0x200004 + (hart)*0x1000) + +void plic_init() +{ + int hart = r_tp(); + // QEMU Virt machine support 7 priority (1 - 7), + // The "0" is reserved, and the lowest priority is "1". + + *(uint32_t *)PLIC_PRIORITY(UART0_IRQ) = 1; + *(uint32_t *)PLIC_PRIORITY(VIRTIO_IRQ) = 1; + + /* Enable UART0 and VIRTIO */ + + *(uint32_t *)PLIC_MENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO_IRQ); + + /* Set priority threshold for UART0. */ + + *(uint32_t *)PLIC_MTHRESHOLD(hart) = 0; + + /* enable machine-mode external interrupts. */ + w_mie(r_mie() | MIE_MEIE); + + // enable machine-mode interrupts. + w_mstatus(r_mstatus() | MSTATUS_MIE); +} + +int plic_claim() +{ + int hart = r_tp(); + int irq = *(uint32_t *)PLIC_MCLAIM(hart); + return irq; +} + +void plic_complete(int irq) +{ + int hart = r_tp(); + *(uint32_t *)PLIC_MCOMPLETE(hart) = irq; +} \ No newline at end of file diff --git a/09-MemoryAllocator/src/start.s b/09-MemoryAllocator/src/start.s new file mode 100644 index 0000000..aec0240 --- /dev/null +++ b/09-MemoryAllocator/src/start.s @@ -0,0 +1,26 @@ +.equ STACK_SIZE, 8192 + +.global _start + +_start: + # setup stacks per hart + csrr t0, mhartid # read current hart id + slli t0, t0, 10 # shift left the hart id by 1024 + la sp, stacks + STACK_SIZE # set the initial stack pointer + # to the end of the stack space + add sp, sp, t0 # move the current hart stack pointer + # to its place in the stack space + + # park harts with id != 0 + csrr a0, mhartid # read current hart id + bnez a0, park # if we're not on the hart 0 + # we park the hart + + j os_main # hart 0 jump to c + +park: + wfi + j park + +stacks: + .skip STACK_SIZE * 4 # allocate space for the harts stacks diff --git a/09-MemoryAllocator/src/string.c b/09-MemoryAllocator/src/string.c new file mode 100644 index 0000000..ae863f4 --- /dev/null +++ b/09-MemoryAllocator/src/string.c @@ -0,0 +1,40 @@ +#include "string.h" + +void *memset(void *dst, int c, unsigned int n) +{ + char *cdst = (char *)dst; + int i; + for (i = 0; i < n; i++) + { + cdst[i] = c; + } + return dst; +} + +void * +memcpy(void *dst, const void *src, unsigned int n) +{ + return memmove(dst, src, n); +} + +void * +memmove(void *dst, const void *src, unsigned int n) +{ + const char *s; + char *d; + + s = src; + 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; +} \ No newline at end of file diff --git a/09-MemoryAllocator/src/sys.s b/09-MemoryAllocator/src/sys.s new file mode 100644 index 0000000..15a6f55 --- /dev/null +++ b/09-MemoryAllocator/src/sys.s @@ -0,0 +1,152 @@ +# This Code derived from xv6-riscv (64bit) +# -- https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/swtch.S + +# ============ MACRO ================== +.macro ctx_save base + sw ra, 0(\base) + sw sp, 4(\base) + sw s0, 8(\base) + sw s1, 12(\base) + sw s2, 16(\base) + sw s3, 20(\base) + sw s4, 24(\base) + sw s5, 28(\base) + sw s6, 32(\base) + sw s7, 36(\base) + sw s8, 40(\base) + sw s9, 44(\base) + sw s10, 48(\base) + sw s11, 52(\base) +.endm + +.macro ctx_load base + lw ra, 0(\base) + lw sp, 4(\base) + lw s0, 8(\base) + lw s1, 12(\base) + lw s2, 16(\base) + lw s3, 20(\base) + lw s4, 24(\base) + lw s5, 28(\base) + lw s6, 32(\base) + lw s7, 36(\base) + lw s8, 40(\base) + lw s9, 44(\base) + lw s10, 48(\base) + lw s11, 52(\base) +.endm + +.macro reg_save base + # save the registers. + sw ra, 0(\base) + sw sp, 4(\base) + sw gp, 8(\base) + sw tp, 12(\base) + sw t0, 16(\base) + sw t1, 20(\base) + sw t2, 24(\base) + sw s0, 28(\base) + sw s1, 32(\base) + sw a0, 36(\base) + sw a1, 40(\base) + sw a2, 44(\base) + sw a3, 48(\base) + sw a4, 52(\base) + sw a5, 56(\base) + sw a6, 60(\base) + sw a7, 64(\base) + sw s2, 68(\base) + sw s3, 72(\base) + sw s4, 76(\base) + sw s5, 80(\base) + sw s6, 84(\base) + sw s7, 88(\base) + sw s8, 92(\base) + sw s9, 96(\base) + sw s10, 100(\base) + sw s11, 104(\base) + sw t3, 108(\base) + sw t4, 112(\base) + sw t5, 116(\base) + sw t6, 120(\base) +.endm + +.macro reg_load base + # restore registers. + lw ra, 0(\base) + lw sp, 4(\base) + lw gp, 8(\base) + # not this, in case we moved CPUs: lw tp, 12(\base) + lw t0, 16(\base) + lw t1, 20(\base) + lw t2, 24(\base) + lw s0, 28(\base) + lw s1, 32(\base) + lw a0, 36(\base) + lw a1, 40(\base) + lw a2, 44(\base) + lw a3, 48(\base) + lw a4, 52(\base) + lw a5, 56(\base) + lw a6, 60(\base) + lw a7, 64(\base) + lw s2, 68(\base) + lw s3, 72(\base) + lw s4, 76(\base) + lw s5, 80(\base) + lw s6, 84(\base) + lw s7, 88(\base) + lw s8, 92(\base) + lw s9, 96(\base) + lw s10, 100(\base) + lw s11, 104(\base) + lw t3, 108(\base) + lw t4, 112(\base) + lw t5, 116(\base) + lw t6, 120(\base) +.endm +# ============ Macro END ================== + +# Context switch +# +# void sys_switch(struct context *old, struct context *new); +# +# Save current registers in old. Load from new. + +.globl sys_switch +.align 4 +sys_switch: + + ctx_save a0 # a0 => struct context *old + ctx_load a1 # a1 => struct context *new + ret # pc=ra; swtch to new task (new->ra) + +.globl atomic_swap +.align 4 +atomic_swap: + li a5, 1 + amoswap.w.aq a5, a5, 0(a0) + mv a0, a5 + ret + +.globl trap_vector +# the trap vector base address must always be aligned on a 4-byte boundary +.align 4 +trap_vector: + # save context(registers). + csrrw t6, mscratch, t6 # swap t6 and mscratch + reg_save t6 + csrw mscratch, t6 + # call the C trap handler in trap.c + csrr a0, mepc + csrr a1, mcause + call trap_handler + + # trap_handler will return the return address via a0. + csrw mepc, a0 + + # load context(registers). + csrr t6, mscratch + reg_load t6 + mret + diff --git a/09-MemoryAllocator/src/task.c b/09-MemoryAllocator/src/task.c new file mode 100644 index 0000000..17a519e --- /dev/null +++ b/09-MemoryAllocator/src/task.c @@ -0,0 +1,32 @@ +#include "task.h" +#include "lib.h" + +uint8_t task_stack[MAX_TASK][STACK_SIZE]; +struct context ctx_os; +struct context ctx_tasks[MAX_TASK]; +struct context *ctx_now; +int taskTop = 0; // total number of task + +// create a new task +int task_create(void (*task)(void)) +{ + int i = taskTop++; + ctx_tasks[i].ra = (reg_t)task; + ctx_tasks[i].sp = (reg_t)&task_stack[i][STACK_SIZE - 1]; + return i; +} + +// switch to task[i] +void task_go(int i) +{ + ctx_now = &ctx_tasks[i]; + sys_switch(&ctx_os, &ctx_tasks[i]); +} + +// switch back to os +void task_os() +{ + struct context *ctx = ctx_now; + ctx_now = &ctx_os; + sys_switch(ctx, &ctx_os); +} diff --git a/09-MemoryAllocator/src/timer.c b/09-MemoryAllocator/src/timer.c new file mode 100644 index 0000000..b2dc42c --- /dev/null +++ b/09-MemoryAllocator/src/timer.c @@ -0,0 +1,38 @@ +#include "timer.h" + +// a scratch area per CPU for machine-mode timer interrupts. +reg_t timer_scratch[NCPU][5]; + +#define interval 20000000 // cycles; about 2 second in qemu. + +void timer_init() +{ + // each CPU has a separate source of timer interrupts. + int id = r_mhartid(); + + // ask the CLINT for a timer interrupt. + // int interval = 1000000; // cycles; about 1/10th second in qemu. + + *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; + + // prepare information in scratch[] for timervec. + // scratch[0..2] : space for timervec to save registers. + // scratch[3] : address of CLINT MTIMECMP register. + // scratch[4] : desired interval (in cycles) between timer interrupts. + reg_t *scratch = &timer_scratch[id][0]; + scratch[3] = CLINT_MTIMECMP(id); + scratch[4] = interval; + w_mscratch((reg_t)scratch); + + // enable machine-mode timer interrupts. + w_mie(r_mie() | MIE_MTIE); +} + +static int timer_count = 0; + +void timer_handler() +{ + lib_printf("timer_handler: %d\n", ++timer_count); + int id = r_mhartid(); + *(reg_t *)CLINT_MTIMECMP(id) = *(reg_t *)CLINT_MTIME + interval; +} diff --git a/09-MemoryAllocator/src/trap.c b/09-MemoryAllocator/src/trap.c new file mode 100644 index 0000000..f46ff85 --- /dev/null +++ b/09-MemoryAllocator/src/trap.c @@ -0,0 +1,91 @@ +#include "os.h" +extern void trap_vector(); +extern void virtio_disk_isr(); +void trap_init() +{ + // set the machine-mode trap handler. + w_mtvec((reg_t)trap_vector); +} + +void external_handler() +{ + int irq = plic_claim(); + if (irq == UART0_IRQ) + { + lib_isr(); + } + else if (irq == VIRTIO_IRQ) + { + lib_puts("Virtio IRQ\n"); + virtio_disk_isr(); + } + else if (irq) + { + lib_printf("unexpected interrupt irq = %d\n", irq); + } + + if (irq) + { + plic_complete(irq); + } +} + +reg_t trap_handler(reg_t epc, reg_t cause) +{ + reg_t return_pc = epc; + reg_t cause_code = cause & 0xfff; + + if (cause & 0x80000000) + { + /* Asynchronous trap - interrupt */ + switch (cause_code) + { + case 3: + /* software interruption */ + break; + case 7: + /* timer interruption */ + // disable machine-mode timer interrupts. + w_mie(r_mie() & ~(1 << 7)); + timer_handler(); + return_pc = (reg_t)&os_kernel; + // enable machine-mode timer interrupts. + w_mie(r_mie() | MIE_MTIE); + break; + case 11: + /* external interruption */ + external_handler(); + break; + default: + lib_puts("unknown async exception!\n"); + break; + } + } + else + { + switch (cause_code) + { + case 2: + lib_puts("Illegal instruction!\n"); + break; + case 5: + lib_puts("Fault load!\n"); + break; + case 7: + lib_puts("Fault store!\n"); + break; + case 11: + lib_puts("Machine mode ecall!\n"); + break; + default: + /* Synchronous trap - exception */ + lib_printf("Sync exceptions! cause code: %d\n", cause_code); + break; + } + for (;;) + { + /* code */ + } + } + return return_pc; +} diff --git a/09-MemoryAllocator/src/user.c b/09-MemoryAllocator/src/user.c new file mode 100644 index 0000000..170a498 --- /dev/null +++ b/09-MemoryAllocator/src/user.c @@ -0,0 +1,60 @@ +#include "os.h" + +int shared_var = 500; + +lock_t lock; + +void user_task0(void) +{ + lib_puts("Task0: Created!\n"); + while (1) + { + lib_puts("Task0: Running...\n"); + lib_delay(1000); + } +} + +void user_task1(void) +{ + lib_puts("Task1: Created!\n"); + while (1) + { + lib_puts("Task1: Running...\n"); + lib_delay(1000); + } +} + +void user_task2(void) +{ + lib_puts("Task2: Created!\n"); + while (1) + { + for (int i = 0; i < 50; i++) + { + lock_acquire(&lock); + shared_var++; + lock_free(&lock); + lib_delay(100); + } + lib_printf("The value of shared_var is: %d \n", shared_var); + } +} + +void user_task3(void) +{ + lib_puts("Task3: Created!\n"); + while (1) + { + lib_puts("Trying to get the lock... \n"); + lock_acquire(&lock); + lib_puts("Get the lock!\n"); + lock_free(&lock); + lib_delay(1000); + } +} + +void user_init() +{ + task_create(&user_task0); + task_create(&user_task1); +} diff --git a/09-MemoryAllocator/src/virtio.c b/09-MemoryAllocator/src/virtio.c new file mode 100644 index 0000000..c251abf --- /dev/null +++ b/09-MemoryAllocator/src/virtio.c @@ -0,0 +1,308 @@ +#include "virtio.h" +#include "os.h" + +#define R(addr) ((volatile uint32 *)(VIRTIO_MMIO_BASE + (addr))) +#define BSIZE 512 // block size +#define PGSHIFT 12 + +struct blk +{ + uint32 dev; + uint32 blockno; + lock_t lock; + int disk; + unsigned char data[BSIZE]; +}; + +static struct disk +{ + char pages[2 * PGSIZE]; + /* descriptor */ + virtq_desc_t *desc; + /* AvailableRing */ + virtq_avail_t *avail; + /* UsedRing */ + virtq_used_t *used; + /* For decord each descriptor is free or not */ + char free[NUM]; + struct + { + struct blk *b; + char status; + } info[NUM]; + uint16 used_idx; + /* Disk command headers */ + virtio_blk_req_t ops[NUM]; + + struct lock vdisk_lock; +} __attribute__((aligned(PGSIZE))) disk; + +struct blk b[3]; + +void virtio_tester(int write) +{ + if (!b[0].dev) + { + lib_puts("buffer init...\n"); + for (size_t i = 0; i < 1; i++) + { + b[i].dev = 1; // always is 1 + b[i].blockno = 1; + for (size_t j = 0; j < BSIZE; j++) + { + b[i].data[j] = 0; + } + + lock_init(&(b[i].lock)); + } + } + + lib_puts("block read...\n"); + virtio_disk_rw(&b[0], write); + size_t i = 0; + for (; i < 10; i++) + { + lib_printf("%x \n", b[0].data[i]); + } + lib_puts("\n"); +} + +void virtio_disk_init() +{ + uint32 status = 0; + + lock_init(&disk.vdisk_lock); + + if (*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 || + *R(VIRTIO_MMIO_VERSION) != 1 || + *R(VIRTIO_MMIO_DEVICE_ID) != 2 || + *R(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551) + { + panic("could not find virtio disk"); + } + /* Reset the device */ + *R(VIRTIO_MMIO_STATUS) = status; + /* Set the ACKNOWLEDGE status bit to the status register. */ + status |= VIRTIO_CONFIG_S_ACKNOWLEDGE; + *R(VIRTIO_MMIO_STATUS) = status; + /* Set the DRIVER status bit to the status register. */ + status |= VIRTIO_CONFIG_S_DRIVER; + *R(VIRTIO_MMIO_STATUS) = status; + /* negotiate features */ + uint32 features = *R(VIRTIO_MMIO_DEVICE_FEATURES); + features &= ~(1 << VIRTIO_BLK_F_RO); + features &= ~(1 << VIRTIO_BLK_F_SCSI); + features &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE); + features &= ~(1 << VIRTIO_BLK_F_MQ); + features &= ~(1 << VIRTIO_F_ANY_LAYOUT); + features &= ~(1 << VIRTIO_RING_F_EVENT_IDX); + features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC); + *R(VIRTIO_MMIO_DRIVER_FEATURES) = features; + + /* tell device that feature negotiation is complete. */ + status |= VIRTIO_CONFIG_S_FEATURES_OK; + *R(VIRTIO_MMIO_STATUS) = status; + + /* tell device we're completely ready. */ + status |= VIRTIO_CONFIG_S_DRIVER_OK; + *R(VIRTIO_MMIO_STATUS) = status; + + *R(VIRTIO_MMIO_GUEST_PAGE_SIZE) = PGSIZE; + /* initialize queue 0. */ + *R(VIRTIO_MMIO_QUEUE_SEL) = 0; + uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX); + if (max == 0) + panic("virtio disk has no queue 0\n"); + if (max < NUM) + panic("virtio disk max queue too short\n"); + *R(VIRTIO_MMIO_QUEUE_NUM) = NUM; + memset(disk.pages, 0, sizeof(disk.pages)); + *R(VIRTIO_MMIO_QUEUE_PFN) = ((uint32)disk.pages) / PGSIZE; + *R(VIRTIO_MMIO_QUEUE_ALIGN) = PGSIZE; + *R(VIRTIO_MMIO_QUEUE_READY) = 1; + // desc = pages -- num * virtq_desc + // avail = pages + 0x40 -- 2 * uint16, then num * uint16 + // used = pages + 4096 -- 2 * uint16, then num * vRingUsedElem + disk.desc = (struct virtq_desc *)disk.pages; + disk.avail = (struct virtq_avail *)(disk.pages + NUM * sizeof(virtq_desc_t)); + disk.used = (struct virtq_used *)(disk.pages + PGSIZE); + + *R(VIRTIO_MMIO_QueueDescLow) = disk.desc; + *R(VIRTIO_MMIO_QueueAvailLow) = disk.avail; + *R(VIRTIO_MMIO_QueueUsedLow) = disk.used; + + // all NUM descriptors start out unused. + for (int i = 0; i < NUM; i++) + disk.free[i] = 1; + lib_puts("Disk init work is success!\n"); +} + +// find a free descriptor, mark it non-free, return its index. +static int +alloc_desc() +{ + for (int i = 0; i < NUM; i++) + { + if (disk.free[i]) + { + disk.free[i] = 0; + return i; + } + } + return -1; +} + +// mark a descriptor as free. +static void +free_desc(int i) +{ + if (i >= NUM) + panic("free_desc 1"); + if (disk.free[i]) + panic("free_desc 2"); + disk.desc[i].addr = 0; + disk.desc[i].len = 0; + disk.desc[i].flags = 0; + disk.desc[i].next = 0; + disk.free[i] = 1; +} + +// free a chain of descriptors. +static void +free_chain(int i) +{ + while (1) + { + int flag = disk.desc[i].flags; + int nxt = disk.desc[i].next; + free_desc(i); + if (flag & VRING_DESC_F_NEXT) + i = nxt; + else + break; + } +} + +// allocate three descriptors (they need not be contiguous). +// disk transfers always use three descriptors. +static int +alloc3_desc(int *idx) +{ + for (int i = 0; i < 3; i++) + { + idx[i] = alloc_desc(); + if (idx[i] < 0) + { + for (int j = 0; j < i; j++) + free_desc(idx[j]); + return -1; + } + } + return 0; +} + +void virtio_disk_rw(struct blk *b, int write) +{ + uint64 sector = b->blockno * (BSIZE / 512); + + lock_acquire(&disk.vdisk_lock); + + // allocate the three descriptors. + int idx[3]; + while (1) + { + if (alloc3_desc(idx) == 0) + { + break; + } + } + + // format the three descriptors. + // qemu's virtio-blk.c reads them. + + virtio_blk_req_t *buf0 = &disk.ops[idx[0]]; + + if (write) + buf0->type = VIRTIO_BLK_T_OUT; // write the disk + else + buf0->type = VIRTIO_BLK_T_IN; // read the disk + buf0->reserved = 0; // The reserved portion is used to pad the header to 16 bytes and move the 32-bit sector field to the correct place. + buf0->sector = sector; // specify the sector that we wanna modified. + + disk.desc[idx[0]].addr = buf0; + disk.desc[idx[0]].len = sizeof(struct virtio_blk_req); + disk.desc[idx[0]].flags = VRING_DESC_F_NEXT; + disk.desc[idx[0]].next = idx[1]; + + disk.desc[idx[1]].addr = ((uint32)b->data) & 0xffffffff; + disk.desc[idx[1]].len = BSIZE; + if (write) + disk.desc[idx[1]].flags = 0; // device reads b->data + else + disk.desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data + disk.desc[idx[1]].flags |= VRING_DESC_F_NEXT; + disk.desc[idx[1]].next = idx[2]; + + disk.info[idx[0]].status = 0; + disk.desc[idx[2]].addr = (uint32)&disk.info[idx[0]].status; + disk.desc[idx[2]].len = 1; + disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; // device writes the status + disk.desc[idx[2]].next = 0; + + // record struct buf for virtio_disk_intr(). + b->disk = 1; + disk.info[idx[0]].b = b; + + __sync_synchronize(); + + // tell the device the first index in our chain of descriptors. + disk.avail->ring[disk.avail->idx % NUM] = idx[0]; + + __sync_synchronize(); + + // tell the device another avail ring entry is available. + disk.avail->idx += 1; // not % NUM ... + + *R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // The device starts immediately when we write 0 to queue_notify. + while (b->disk == 1) + { + } + + disk.info[idx[0]].b = 0; + free_chain(idx[0]); + + lock_free(&disk.vdisk_lock); +} + +void virtio_disk_isr() +{ + //lock_acquire(&disk.vdisk_lock); + + // the device won't raise another interrupt until we tell it + // we've seen this interrupt, which the following line does. + // this may race with the device writing new entries to + // the "used" ring, in which case we may process the new + // completion entries in this interrupt, and have nothing to do + // in the next interrupt, which is harmless. + *R(VIRTIO_MMIO_INTERRUPT_ACK) = *R(VIRTIO_MMIO_INTERRUPT_STATUS) & 0x3; + + __sync_synchronize(); + + // the device increments disk.used->idx when it + // adds an entry to the used ring. + + while (disk.used_idx != disk.used->idx) + { + __sync_synchronize(); + int id = disk.used->ring[disk.used_idx % NUM].id; + + if (disk.info[id].status != 0) + panic("virtio_disk_intr status"); + + struct blk *b = disk.info[id].b; + b->disk = 0; + disk.used_idx += 1; + } + + //lock_free(&disk.vdisk_lock); +} \ No newline at end of file From 2fe5bc82878efea989e1dc25fa334d722c531c1b Mon Sep 17 00:00:00 2001 From: ianchen0119 Date: Sat, 7 Aug 2021 23:22:50 +0800 Subject: [PATCH 2/3] modified memory allocator and add references --- 08-BlockDeviceDriver/virtio.c | 4 ++++ 09-MemoryAllocator/README.md | 15 ++++++++------- 09-MemoryAllocator/include/os.h | 4 ++-- 09-MemoryAllocator/src/alloc.c | 31 +++++++++++++++++++++---------- 09-MemoryAllocator/src/os.c | 1 + 09-MemoryAllocator/src/virtio.c | 4 ++++ 6 files changed, 40 insertions(+), 19 deletions(-) diff --git a/08-BlockDeviceDriver/virtio.c b/08-BlockDeviceDriver/virtio.c index c251abf..6454ad6 100644 --- a/08-BlockDeviceDriver/virtio.c +++ b/08-BlockDeviceDriver/virtio.c @@ -1,3 +1,7 @@ +/* This Code derived from xv6-riscv (64bit) + * -- https://github.com/mit-pdos/xv6-riscv/ + */ + #include "virtio.h" #include "os.h" diff --git a/09-MemoryAllocator/README.md b/09-MemoryAllocator/README.md index fe209cb..ac04f14 100644 --- a/09-MemoryAllocator/README.md +++ b/09-MemoryAllocator/README.md @@ -3,18 +3,18 @@ ## Build & Run ```sh -IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/09-MemoryAllocator (master) +IAN@DESKTOP-9AEMEPL MINGW64 ~/Desktop/mini-riscv-os/09-MemoryAllocator (feat/memoryAlloc) $ make all rm -f *.elf *.img riscv64-unknown-elf-gcc -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -T os.ld -o os.elf src/start.s src/sys.s src/mem.s src/lib.c src/timer.c src/task.c src/os.c src/user.c src/trap.c src/lock.c src/plic.c src/virtio.c src/string.c src/alloc.c Press Ctrl-A and then X to exit QEMU qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf -HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 32742 -TEXT: 0x80000000 -> 0x8000ac5c -RODATA: 0x8000ac5c -> 0x8000b083 +HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 524007 +TEXT: 0x80000000 -> 0x8000ac8c +RODATA: 0x8000ac8c -> 0x8000b0bb DATA: 0x8000c000 -> 0x8000c004 BSS: 0x8000d000 -> 0x8001100c -HEAP: 0x8001a000 -> 0x88000000 +HEAP: 0x80011900 -> 0x88000000 OS start Disk init work is success! buffer init... @@ -31,6 +31,9 @@ Virtio IRQ 00000022 0000008e +p = 0x80011f00 +p2 = 0x80011b00 +p3 = 0x80011900 OS: Activate next task Task0: Created! Task0: Running... @@ -38,8 +41,6 @@ Task0: Running... Task0: Running... Task0: Running... Task0: Running... -Task0: Running... -Task0: Running... QEMU: Terminated ``` diff --git a/09-MemoryAllocator/include/os.h b/09-MemoryAllocator/include/os.h index 8caa046..37cf21c 100644 --- a/09-MemoryAllocator/include/os.h +++ b/09-MemoryAllocator/include/os.h @@ -36,7 +36,7 @@ extern void lock_free(lock_t *lock); // memory allocator -extern void *page_alloc(int npages); -extern void page_free(void *p); +extern void *malloc(size_t size); +extern void free(void *p); #endif diff --git a/09-MemoryAllocator/src/alloc.c b/09-MemoryAllocator/src/alloc.c index 741c4eb..a1e7e83 100644 --- a/09-MemoryAllocator/src/alloc.c +++ b/09-MemoryAllocator/src/alloc.c @@ -1,3 +1,7 @@ +/* This Code derived from RVOS + * -- https://github.com/plctlab/riscv-operating-system-mooc + */ + #include "os.h" extern uint32_t TEXT_START; @@ -21,11 +25,13 @@ static uint32_t _alloc_start = 0; static uint32_t _alloc_end = 0; static uint32_t _num_pages = 0; -#define PAGE_SIZE 4096 -#define PAGE_ORDER 12 +#define PAGE_SIZE 256 +#define PAGE_ORDER 8 #define PAGE_TAKEN (uint8_t)(1 << 0) #define PAGE_LAST (uint8_t)(1 << 1) +#define abs(x) (x > 0) ? (x) : (-x) +#define pageNum(x) ((abs(x)) / ((PAGE_SIZE) + 1)) + 1 /* * Page Descriptor @@ -35,6 +41,7 @@ static uint32_t _num_pages = 0; * - 10: This means this page hasn't been allocated and is the last page of the memory block allocated * - 11: This means this page was allocated and is the last page of the memory block allocated */ + struct Page { uint8_t flags; @@ -113,9 +120,9 @@ void page_init() * Allocate a memory block which is composed of contiguous physical pages * - npages: the number of PAGE_SIZE pages to allocate */ -void *page_alloc(int npages) +void *malloc(size_t size) { - /* Note we are searching the page descriptor bitmaps. */ + int npages = pageNum(size); int found = 0; struct Page *page_i = (struct Page *)HEAP_START; for (int i = 0; i < (_num_pages - npages); i++) @@ -123,10 +130,12 @@ void *page_alloc(int npages) if (_is_free(page_i)) { found = 1; + /* * meet a free page, continue to check if following * (npages - 1) pages are also unallocated. */ + struct Page *page_j = page_i; for (int j = i; j < (i + npages); j++) { @@ -164,7 +173,7 @@ void *page_alloc(int npages) * Free the memory block * - p: start address of the memory block */ -void page_free(void *p) +void free(void *p) { /* * Assert (TBD) if p is invalid @@ -195,14 +204,16 @@ void page_free(void *p) void page_test() { - void *p = page_alloc(2); + void *p = malloc(1024); lib_printf("p = 0x%x\n", p); - page_free(p); - void *p2 = page_alloc(7); + void *p2 = malloc(512); lib_printf("p2 = 0x%x\n", p2); - page_free(p2); - void *p3 = page_alloc(4); + void *p3 = malloc(sizeof(int)); lib_printf("p3 = 0x%x\n", p3); + + free(p); + free(p2); + free(p3); } \ No newline at end of file diff --git a/09-MemoryAllocator/src/os.c b/09-MemoryAllocator/src/os.c index 0995e4f..7f4e4bf 100644 --- a/09-MemoryAllocator/src/os.c +++ b/09-MemoryAllocator/src/os.c @@ -38,6 +38,7 @@ int os_main(void) { os_start(); disk_read(); + page_test(); int current_task = 0; while (1) { diff --git a/09-MemoryAllocator/src/virtio.c b/09-MemoryAllocator/src/virtio.c index c251abf..6454ad6 100644 --- a/09-MemoryAllocator/src/virtio.c +++ b/09-MemoryAllocator/src/virtio.c @@ -1,3 +1,7 @@ +/* This Code derived from xv6-riscv (64bit) + * -- https://github.com/mit-pdos/xv6-riscv/ + */ + #include "virtio.h" #include "os.h" From 9d2d0e3064f19ef24eb9dad43de82cb74dc97ba9 Mon Sep 17 00:00:00 2001 From: ianchen0119 Date: Sun, 8 Aug 2021 16:24:15 +0800 Subject: [PATCH 3/3] add the doc of ch9 --- 09-MemoryAllocator/README.md | 21 ++- 09-MemoryAllocator/src/alloc.c | 16 +- README.md | 2 + doc/tw/09-MemoryAllocator.md | 279 +++++++++++++++++++++++++++++++++ 4 files changed, 301 insertions(+), 17 deletions(-) create mode 100644 doc/tw/09-MemoryAllocator.md diff --git a/09-MemoryAllocator/README.md b/09-MemoryAllocator/README.md index ac04f14..0faef0a 100644 --- a/09-MemoryAllocator/README.md +++ b/09-MemoryAllocator/README.md @@ -9,12 +9,12 @@ rm -f *.elf *.img riscv64-unknown-elf-gcc -I./include -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall -w -T os.ld -o os.elf src/start.s src/sys.s src/mem.s src/lib.c src/timer.c src/task.c src/os.c src/user.c src/trap.c src/lock.c src/plic.c src/virtio.c src/string.c src/alloc.c Press Ctrl-A and then X to exit QEMU qemu-system-riscv32 -nographic -smp 4 -machine virt -bios none -drive if=none,format=raw,file=hdd.dsk,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 -kernel os.elf -HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 524007 -TEXT: 0x80000000 -> 0x8000ac8c -RODATA: 0x8000ac8c -> 0x8000b0bb +HEAP_START = 8001100c, HEAP_SIZE = 07feeff4, num of pages = 521967 +TEXT: 0x80000000 -> 0x8000ac78 +RODATA: 0x8000ac78 -> 0x8000b09f DATA: 0x8000c000 -> 0x8000c004 BSS: 0x8000d000 -> 0x8001100c -HEAP: 0x80011900 -> 0x88000000 +HEAP: 0x80091100 -> 0x88000000 OS start Disk init work is success! buffer init... @@ -31,9 +31,9 @@ Virtio IRQ 00000022 0000008e -p = 0x80011f00 -p2 = 0x80011b00 -p3 = 0x80011900 +p = 0x80091700 +p2 = 0x80091300 +p3 = 0x80091100 OS: Activate next task Task0: Created! Task0: Running... @@ -41,6 +41,13 @@ Task0: Running... Task0: Running... Task0: Running... Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... +Task0: Running... QEMU: Terminated ``` diff --git a/09-MemoryAllocator/src/alloc.c b/09-MemoryAllocator/src/alloc.c index a1e7e83..562d221 100644 --- a/09-MemoryAllocator/src/alloc.c +++ b/09-MemoryAllocator/src/alloc.c @@ -30,15 +30,13 @@ static uint32_t _num_pages = 0; #define PAGE_TAKEN (uint8_t)(1 << 0) #define PAGE_LAST (uint8_t)(1 << 1) -#define abs(x) (x > 0) ? (x) : (-x) -#define pageNum(x) ((abs(x)) / ((PAGE_SIZE) + 1)) + 1 +#define pageNum(x) ((x) / ((PAGE_SIZE) + 1)) + 1 /* * Page Descriptor * flags: * - 00: This means this page hasn't been allocated * - 01: This means this page was allocated - * - 10: This means this page hasn't been allocated and is the last page of the memory block allocated * - 11: This means this page was allocated and is the last page of the memory block allocated */ @@ -82,8 +80,9 @@ static inline int _is_last(struct Page *page) } /* - * align the address to the border of page(4K) + * align the address to the border of page (256) */ + static inline uint32_t _align_page(uint32_t address) { uint32_t order = (1 << PAGE_ORDER) - 1; @@ -92,11 +91,7 @@ static inline uint32_t _align_page(uint32_t address) void page_init() { - /* - * We reserved 8 Page (8 x 4096) to hold the Page structures. - * It should be enough to manage at most 128 MB (8 x 4096 x 4096) - */ - _num_pages = (HEAP_SIZE / PAGE_SIZE) - 8; + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 2048; lib_printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); struct Page *page = (struct Page *)HEAP_START; @@ -106,7 +101,7 @@ void page_init() page++; } - _alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE); + _alloc_start = _align_page(HEAP_START + 2048 * PAGE_SIZE); _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); lib_printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); @@ -120,6 +115,7 @@ void page_init() * Allocate a memory block which is composed of contiguous physical pages * - npages: the number of PAGE_SIZE pages to allocate */ + void *malloc(size_t size) { int npages = pageNum(size); diff --git a/README.md b/README.md index cd96dcd..25a80ea 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ And you should start your git-bash to build the project. (It works for me in vsc - Learing PLIC & external interruption - [08-BlockDeviceDriver](08-BlockDeviceDriver) - Learning VirtIO Protocol & Device driver implementation +- [09-MemoryAllocator](09-MemoryAllocator) + - Understanding how to write the linker script & how heap works ## Building and Verification diff --git a/doc/tw/09-MemoryAllocator.md b/doc/tw/09-MemoryAllocator.md new file mode 100644 index 0000000..8483297 --- /dev/null +++ b/doc/tw/09-MemoryAllocator.md @@ -0,0 +1,279 @@ +# 09-MemoryAllocator -- RISC-V 的嵌入式作業系統 + +## 先備知識: Linker Script 撰寫 + +撰寫 Linker Script 可以讓編譯器在連結的階段按照我們的想法將每個 Section 放到指令的記憶體位址上。 + +![](https://camo.githubusercontent.com/1d58b18d5a293fe858931e54cce54ac53f4e86b08da25de332a16434688e7434/68747470733a2f2f692e696d6775722e636f6d2f756f72425063642e706e67) + +以上圖為例,系統程式在執行時會將每個區段放到記憶體當中,至於到底要分配哪些區段,每個區段的屬性 (可讀可寫可執行) 就需要使用 Linker Script 來告訴編譯器了! + +### 語法教學: 入口點與架構 + +看到本專案的 `os.ld`,即為 mini-riscv-os 的 Linker Script: + +``` +OUTPUT_ARCH( "riscv" ) + +ENTRY( _start ) + +MEMORY +{ + ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M +} +``` + +觀察上面的腳本,可以歸納出幾個小結論: + +- 輸出的執行檔會執行在 `riscv` 平台 +- 程式的進入點為 `_start` +- 該記憶體名稱為 `ram`,其屬性為: + - [x] W (可寫) + - [x] X (可執行) + - [x] A (可分配) + - [ ] R (唯讀) + - [ ] I (初始化) +- 記憶體的起點為 0x80000000,長度為 128 MB,換言之,記憶體範圍為: 0x08000000 - 0x88000000。 + +接著,可以看到 Linker Script 在 SECTION 段之中切割了好幾的區段,分別是: + +- .text +- .rodata +- .data +- .bss + +以其中一段的範例對腳本進行解說: + +``` +.text : { + PROVIDE(_text_start = .); + *(.text.init) *(.text .text.*) + PROVIDE(_text_end = .); + } >ram AT>ram :text +``` + +- `PROVIDE` 可以幫助我們定義符號,這些符號也都代表著一個記憶體位址 +- `*(.text.init) *(.text .text.*)` 幫助我們配對任何的 object file 中的 .text 區段。 +- `>ram AT>ram :text` + + - ram 為 VMA (Virtual Memory Address),當程式運作時,section 會得到這個記憶體位址。 + - ram :text 為 LMA (Load Memory Address),當該區段被載入時,會被放到這個記憶體位址。 + + 最後,Linker Script 還定義了起始與終點的 Symbol 以及 Heap 的位置: + + ``` + PROVIDE(_memory_start = ORIGIN(ram)); + PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram)); + + PROVIDE(_heap_start = _bss_end); + PROVIDE(_heap_size = _memory_end - _heap_start); + ``` + +如果用圖片表示,記憶體的分布狀況如下: + +![](https://i.imgur.com/NCJ3BgL.png) + +## 進入正題 + +### Heap 是什麼? + +本文中所提到的 Heap 與資料結構的 Heap 不同,這邊的 Heap 是指可供作業系統與 Process 分配的記憶體空間,我們都知道, Stack 會存放已經初始化的固定長度資料,比起 Stack , Heap 有了更多彈性,我們想要使用多少空間就分配多少空間,並且在使用後可以進行記憶體回收避免浪費。 + +```c= +#include +int *p = (int*) malloc(sizeof(int)); +// ... +free(p); +``` + +上面的 C 語言範例便是使用 `malloc()` 進行動態記憶體的配置,並且在使用完畢後呼叫 `free()` 對記憶體進行回收。 + +### mini-riscv-os 的實作 + +了解 Heap 與 Linker Script 所描述出的記憶體結構後,就要進入本文的重點了! +在這次的單元中,我們特別切割出一段空間供 Heap 使用,這樣一來,在系統程式中也可以實現類似於 Memory Allocator 的功能: + +```assembly +.section .rodata +.global HEAP_START +HEAP_START: .word _heap_start + +.global HEAP_SIZE +HEAP_SIZE: .word _heap_size + +.global TEXT_START +TEXT_START: .word _text_start + +.global TEXT_END +TEXT_END: .word _text_end + +.global DATA_START +DATA_START: .word _data_start + +.global DATA_END +DATA_END: .word _data_end + +.global RODATA_START +RODATA_START: .word _rodata_start + +.global RODATA_END +RODATA_END: .word _rodata_end + +.global BSS_START +BSS_START: .word _bss_start + +.global BSS_END +BSS_END: .word _bss_end +``` + +在 `mem.s` 中,我們宣告多個變數,每個變數都代表了先前在 Linker Script 定義的 Symbol,這樣一來,我們就可以在 C 程式中取用這些記憶體位址: + +```cpp +extern uint32_t TEXT_START; +extern uint32_t TEXT_END; +extern uint32_t DATA_START; +extern uint32_t DATA_END; +extern uint32_t RODATA_START; +extern uint32_t RODATA_END; +extern uint32_t BSS_START; +extern uint32_t BSS_END; +extern uint32_t HEAP_START; +extern uint32_t HEAP_SIZE; +``` + +### 如何管理記憶體區塊 + +其實在主流的作業系統中,Heap 的結構非常複雜,會有多個列表管理未被分配的記憶體區塊以及不同大小的已分配記憶體區塊。 + +```cpp +static uint32_t _alloc_start = 0; +static uint32_t _alloc_end = 0; +static uint32_t _num_pages = 0; + +#define PAGE_SIZE 256 +#define PAGE_ORDER 8 +``` + +在 mini-riscv-os 中,我們統一區塊的大小為 25b Bits,也就是說,當我們呼叫 `malloc(sizeof(int))` 時,他也會一口氣分配 256 Bits 的空間給這個請求。 + +```cpp +void page_init() +{ + _num_pages = (HEAP_SIZE / PAGE_SIZE) - 2048; + lib_printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); + + struct Page *page = (struct Page *)HEAP_START; + for (int i = 0; i < _num_pages; i++) + { + _clear(page); + page++; + } + + _alloc_start = _align_page(HEAP_START + 2048 * PAGE_SIZE); + _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); + + lib_printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END); + lib_printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END); + lib_printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END); + lib_printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END); + lib_printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); +} +``` + +在 `page_init()` 當中可以看到,假設有 N 個 256 Bits 的記憶體區塊可供分配,我們勢必要實作出一個資料結構來管理記憶體區塊的狀態: + +```cpp +struct Page +{ + uint8_t flags; +}; +``` + +因此,Heap 的記憶體會被用來存放: N 個 Page Struct 以及 N 個 256 Bits 的記憶體區塊,呈現一對一的關係。 +至於,要如何分辨第 A 個記憶體區塊是否被分配,就要看與之對應的 Page Struct 中的 flag 記錄了什麼: + +- 00: This means this page hasn't been allocated +- 01: This means this page was allocated +- 11: This means this page was allocated and is the last page of the memory block allocated + +`00` 與 `01` 的狀態十分易懂,至於 `11` 會在哪種情況用到呢?我們繼續往下看: + +```cpp +void *malloc(size_t size) +{ + int npages = pageNum(size); + int found = 0; + struct Page *page_i = (struct Page *)HEAP_START; + for (int i = 0; i < (_num_pages - npages); i++) + { + if (_is_free(page_i)) + { + found = 1; + + /* + * meet a free page, continue to check if following + * (npages - 1) pages are also unallocated. + */ + + struct Page *page_j = page_i; + for (int j = i; j < (i + npages); j++) + { + if (!_is_free(page_j)) + { + found = 0; + break; + } + page_j++; + } + /* + * get a memory block which is good enough for us, + * take housekeeping, then return the actual start + * address of the first page of this memory block + */ + if (found) + { + struct Page *page_k = page_i; + for (int k = i; k < (i + npages); k++) + { + _set_flag(page_k, PAGE_TAKEN); + page_k++; + } + page_k--; + _set_flag(page_k, PAGE_LAST); + return (void *)(_alloc_start + i * PAGE_SIZE); + } + } + page_i++; + } + return NULL; +} +``` + +透過閱讀 `malloc()` 的原始碼可以得知,當使用者裡用它嘗試獲取大於 256 Bits 的記憶體空間時,他會先計算請求的記憶體大小需要多少區塊才能滿足。計算完成後,他會在連續的記憶體區塊中尋找相連且未分配的記憶體區塊分配給該請求: + +``` +malloc(513); +Cause 513 Bits > The size of the 2 blocks, +thus, malloc will allocates 3 blocks for the request. + +Before Allocation: + ++----+ +----+ +----+ +----+ +----+ +| 00 | -> | 00 | -> | 00 | -> | 00 | -> | 00 | ++----+ +----+ +----+ +----+ +----+ + +After Allocation: + ++----+ +----+ +----+ +----+ +----+ +| 01 | -> | 01 | -> | 11 | -> | 00 | -> | 00 | ++----+ +----+ +----+ +----+ +----+ + +``` + +分配完成後,我們可以發現最後一個被分配的記憶體區塊的 Flag 為 `11`,這樣一來,等到使用者呼叫 `free()` 釋放這塊記憶體時,系統就可以透過 Flag 確認帶有 `11` 標記的區塊是需要被釋放的最後一塊區塊。 + +## Reference + +- [10 分鐘讀懂 linker scripts](https://blog.louie.lu/2016/11/06/10%E5%88%86%E9%90%98%E8%AE%80%E6%87%82-linker-scripts/) +- [Step by step, learn to develop an operating system on RISC-V](https://github.com/plctlab/riscv-operating-system-mooc) +- [Heap Exploitation](https://github.com/ianchen0119/About-Security/wiki/Heap-Exploitation)