# 1. Hello World OS ## ENV SETUP ### 安裝一臺 Debian Workstation 0. 灌系統 1. 安裝開發環境,筆者使用的是 VIM ### 安裝測試環境: 0. 安裝 git 1. 安裝 risc-v 工具鏈 ``` sudo apt-get install gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu ``` 2. 安裝 qemu ``` sudo apt install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \ gawk build-essential bison flex texinfo gperf libtool patchutils bc \ zlib1g-dev libexpat-dev \ libglib2.0-dev libpixman-1-dev git clone https://github.com/qemu/qemu cd qemu ./configure --target-list=riscv32-softmmu,riscv64-softmmu make sudo make install ``` ### 編譯一個現成的 OS 在安裝完 qemu 之後,我們可以嘗試編譯并且執行一個 risc-v 的程式。 筆者這邊選擇的 OS 是基於 risc-v 的教學型 OS - [xv6](https://github.com/mit-pdos/xv6-riscv)。 ``` git clone https://github.com/mit-pdos/xv6-riscv.git cd xv6-riscv make qemu ``` ![](https://hackmd.io/_uploads/Bku9vEVb6.png) 可以按下 ctrl + a 后按 x 離開 qemu。 感興趣的可以去看一下 Makefile 當中他是如何呼叫 qemu 的,我們留到稍後敘述。 ## 來寫我們的小小 OS ### os.c ```c= volatile char *uart = (volatile char*) 0x10000000; void print(const char *str) { while (*str) { *uart = *(str++); } } int os_main(void) { print("Hello, QEMU!\n"); while (1) {} return 0; } ``` ### bootloader.s ```risc-v= .section .text.start .globl _start _start: csrr t0, mhartid lui t1, 0 bne t0, t1, park # 僅讓一核心執行我們的程式,其他進入 park 空等 la sp, stack_top call os_main park: wfi j park ``` ### link.ld ```= 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 : { *(.text.start); PROVIDE(_text_start = .); *(.text.init) *(.text .text.*) PROVIDE(_text_end = .); } >ram AT>ram :text . = ALIGN(0x1000); etext = .; .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)); . += 0x8000; stack_top = .; end = .; } ``` ### Makefile ```= SHELL = /bin/sh ARCH = riscv64-linux-gnu CC = $(ARCH)-gcc LD = $(ARCH)-ld OBJCOPY = $(ARCH)-objcopy SDIR = src BDIR = build OBJCOPY = $(ARM)-objcopy S_SRCS = $(wildcard $(SDIR)/*.s) C_SRCS = $(wildcard $(SDIR)/*.c) S_OBJS = $(S_SRCS:$(SDIR)/%.s=$(BDIR)/%_asm.o) C_OBJS = $(C_SRCS:$(SDIR)/%.c=$(BDIR)/%.o) all: clean kernel.img kernel.img: kernel.elf $(OBJCOPY) kernel.elf -I binary kernel.img kernel.elf: $(S_OBJS) link.ld $(C_OBJS) $(LD) -T link.ld -o kernel.elf $(S_OBJS) $(C_OBJS) $(BDIR)/%.o: $(SDIR)/%.c $(CC) $(CFLAGS) -c $< -o $@ $(BDIR)/%_asm.o: $(SDIR)/%.s $(CC) $(SFLAGS) -c $< -o $@ clean: rm -f $(BDIR)/*_asm.o $(BDIR)/*.o kernel.elf kernel.img run: all qemu-system-riscv64 -M virt -kernel kernel.img -bios none -serial stdio -display none ``` ### 目錄圖如下 ![](https://hackmd.io/_uploads/HJRew3NWp.png) ### 測試并執行 ``` make run ``` ![](https://hackmd.io/_uploads/Bkqfv3Nbp.png) ## 撰寫簡單功能的 printf ### printf.h ```c= #include <stdarg.h> void putchar(char c); void print_int(long long value); void printf(const char *format, ...); ``` ### printf.c ```c= #include "printf.h" volatile char *uart = (volatile char*) 0x10000000; void putchar(char c) { *uart = c; } void print_int(long long value) { if (value == 0) { putchar('0'); return; } if (value < 0) { putchar('-'); value = -value; } char buffer[20]; int i = 0; while (value) { buffer[i++] = (value % 10) + '0'; value /= 10; } while (i-- > 0) { putchar(buffer[i]); } } void printf(const char *format, ...) { va_list args; va_start(args, format); while (*format) { if (*format == '%') { format++; switch (*format) { case 's': { char *str = va_arg(args, char*); while (*str) { putchar(*str++); } break; } case 'd': { int value = va_arg(args, int); print_int(value); break; } default: putchar(*format); break; } } else { putchar(*format); } format++; } va_end(args); } ``` ### os.c ```c= #include "printf.h" int os_main(void) { printf("%s, %d\n", "Hello World", 8964); while(1); return 0; } ``` ### Makefile 新增編譯 header file 的部分。 ``` SHELL = /bin/sh ARCH = riscv64-linux-gnu CC = $(ARCH)-gcc LD = $(ARCH)-ld OBJCOPY = $(ARCH)-objcopy IDIR = inc SDIR = src BDIR = build CFLAGS = -fno-builtin -nostdlib -Wall -mcmodel=medany -g -I $(IDIR) -O0 SFLAGS = -g -I $(IDIR) OBJCOPY = $(ARM)-objcopy S_SRCS = $(wildcard $(SDIR)/*.s) C_SRCS = $(wildcard $(SDIR)/*.c) S_OBJS = $(S_SRCS:$(SDIR)/%.s=$(BDIR)/%_asm.o) C_OBJS = $(C_SRCS:$(SDIR)/%.c=$(BDIR)/%.o) all: clean kernel.img kernel.img: kernel.elf $(OBJCOPY) kernel.elf -I binary kernel.img kernel.elf: $(S_OBJS) link.ld $(C_OBJS) $(LD) -T link.ld -o kernel.elf $(S_OBJS) $(C_OBJS) $(BDIR)/%.o: $(SDIR)/%.c $(CC) $(CFLAGS) -c $< -o $@ $(BDIR)/%_asm.o: $(SDIR)/%.s $(CC) $(SFLAGS) -c $< -o $@ clean: rm -f $(BDIR)/*_asm.o $(BDIR)/*.o kernel.elf kernel.img run: all qemu-system-riscv64 -M virt -kernel kernel.img -bios none -serial stdio -display none ``` ### 目錄結構 ![](https://hackmd.io/_uploads/ryokv6VbT.png) ## 記憶體分配 ### link.ld 將 link.ld 修改為如下: ``` /* reserve 0x8000 bytes for stack */ . += 0x8000; stack_top = .; end = .; . += 0x200; _heap_start = .; PROVIDE(_heap_start = _bss_end); PROVIDE(_heap_size = _memory_end - _heap_start); ``` ### mem.s 因為我們在 C 是使用 void* 來存取 `_heap_start`,而我們使用的虛擬機是 64 位元的,因此需要 2 個 word(8 bytes)。 ```risc-v= .section .rodata .global HEAP_START HEAP_START: .word _heap_start .word 0x0 .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 BSS_START BSS_START: .word _bss_start .global BSS_END BSS_END: .word _bss_end ``` ### mem.h ```c= #define PAGE_SIZE 4096 #include "printf.h" #include <stddef.h> #include <stdint.h> extern uint32_t TEXT_START; extern uint32_t TEXT_END; extern uint32_t DATA_START; extern uint32_t DATA_END; extern uint32_t BSS_START; extern uint32_t BSS_END; extern void *HEAP_START; extern uint32_t HEAP_SIZE; struct Page { int8_t flag; void* ptr; }; void page_init(); void printMEM(int x); void* malloc(size_t size); int free(void *ptr); ``` ### mem.c 這邊簡單實作了一個 malloc 函式,沒有用 linked-list 作優化,每次都是暴力搜索可用區段。 ```c= #include "mem.h" static void *_alloc_start = 0; static void *_alloc_end = 0; static uint32_t _num_pages = 0; void _clear(void* ptr, size_t size) { unsigned char* byte_ptr = (unsigned char*)ptr; for(size_t i = 0; i < size; i++) { byte_ptr[i] = 0; } } void page_init() { _num_pages = HEAP_SIZE / (PAGE_SIZE + sizeof(struct Page)); printf("HEAP_START =0x%x, HEAP_SIZE = 0x%x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages); _alloc_start = HEAP_START + _num_pages * sizeof(struct Page); _alloc_end = _alloc_start + (PAGE_SIZE * _num_pages); struct Page *page = (struct Page *)HEAP_START; /* printf("%x\n", page -> flag); */ for (uint32_t i = 0; i < _num_pages; i++) { /* printf("%x\n", page->flag); */ page->flag = 0; page->ptr = _alloc_start + i * PAGE_SIZE; page++; } printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end); } void printMEM(int x) { struct Page *page = (struct Page *)HEAP_START; for(int i = 0; i < x; i++, page++) { printf("| %d ", page -> flag); } printf("|\n"); } void* malloc(size_t size) { int N = (size + PAGE_SIZE - 1) / PAGE_SIZE; // ceil struct Page *page = (struct Page *)HEAP_START; for(int i = 0; i < _num_pages; i++, page++) { struct Page *start_pos = page; int j; for(j = 0; j < N; j++, i++, page++) { if(page->flag != 0) { break; } } if(j == N) { page = start_pos; for(int j = 0; j < N; j++, page++) { page->flag = 1; } page--; page->flag = 2; return start_pos->ptr; } } return 0x0; } int free(void* ptr) { struct Page *page = (struct Page *)HEAP_START; for(int i = 0; i < _num_pages; i++, page++) { if(page->ptr == ptr) break; if(page->ptr > ptr) return 1; } while(page->flag == 1) { _clear(page->ptr, PAGE_SIZE); page->flag = 0; page++; } _clear(page->ptr, PAGE_SIZE); page->flag = 0; return 0; } ``` ### os.c ```c= #include "printf.h" #include "mem.h" int os_main(void) { page_init(); int* arr1 = malloc(sizeof(int) * 4096); int* arr2 = malloc(sizeof(int) * 4096); printMEM(10); printf("0x%x, 0x%x\n", arr1, arr2); free(arr1); printMEM(10); printf("0x%x 0x%x 0x%x\n", malloc(sizeof(int) * 1024), malloc(sizeof(int) * 1024), malloc(sizeof(int) * 1024)); printMEM(10); while(1); return 0; } ``` ## 參考資料 1. ChatGPT 2. [s094392/riscv-bare-metal](https://github.com/s094392/riscv-bare-metal/tree/master) 3. [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/) 4. [cccriscv/mini-riscv-os](https://github.com/cccriscv/mini-riscv-os/) ## GitHub [REPO](https://github.com/LeeLin2602/lin-os-study)