Merge pull request #12 from ianchen0119/feat/memoryAlloc

Add memory allocator
This commit is contained in:
Ian Chen
2021-08-08 16:24:57 +08:00
committed by GitHub
30 changed files with 2268 additions and 0 deletions

View File

@@ -1,3 +1,7 @@
/* This Code derived from xv6-riscv (64bit)
* -- https://github.com/mit-pdos/xv6-riscv/
*/
#include "virtio.h"
#include "os.h"

View File

@@ -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

View File

@@ -0,0 +1,83 @@
# 09-MemoryAllocator
## Build & Run
```sh
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 = 521967
TEXT: 0x80000000 -> 0x8000ac78
RODATA: 0x8000ac78 -> 0x8000b09f
DATA: 0x8000c000 -> 0x8000c004
BSS: 0x8000d000 -> 0x8001100c
HEAP: 0x80091100 -> 0x88000000
OS start
Disk init work is success!
buffer init...
block read...
Virtio IRQ
000000fd
000000af
000000f8
000000ab
00000088
00000042
000000cc
00000017
00000022
0000008e
p = 0x80091700
p2 = 0x80091300
p3 = 0x80091100
OS: Activate next task
Task0: Created!
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
Task0: Running...
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).

View File

@@ -0,0 +1,4 @@
set disassemble-next-line on
b _start
target remote : 1234
c

View File

@@ -0,0 +1,28 @@
#ifndef __LIB_H__
#define __LIB_H__
#include "riscv.h"
#include <stddef.h>
#include <stdarg.h>
#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

View File

@@ -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 *malloc(size_t size);
extern void free(void *p);
#endif

View File

@@ -0,0 +1,162 @@
#ifndef __RISCV_H__
#define __RISCV_H__
#include <stdint.h>
#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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

49
09-MemoryAllocator/os.ld Normal file
View File

@@ -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);
}

View File

@@ -0,0 +1,215 @@
/* This Code derived from RVOS
* -- https://github.com/plctlab/riscv-operating-system-mooc
*/
#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 256
#define PAGE_ORDER 8
#define PAGE_TAKEN (uint8_t)(1 << 0)
#define PAGE_LAST (uint8_t)(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
* - 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 (256)
*/
static inline uint32_t _align_page(uint32_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
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);
}
/*
* 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);
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 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 = malloc(1024);
lib_printf("p = 0x%x\n", p);
void *p2 = malloc(512);
lib_printf("p2 = 0x%x\n", p2);
void *p3 = malloc(sizeof(int));
lib_printf("p3 = 0x%x\n", p3);
free(p);
free(p2);
free(p3);
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -0,0 +1,52 @@
#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();
page_test();
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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -0,0 +1,312 @@
/* This Code derived from xv6-riscv (64bit)
* -- https://github.com/mit-pdos/xv6-riscv/
*/
#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);
}

View File

@@ -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

View File

@@ -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 <stdlib.h>
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)