ZYB ARTICLES REPOS

如何将TencentOS tiny移植到RISC-V(二)

书接上文,先把TencentOS tiny代码拉下来

git clone https://github.com/Tencent/TencentOS-tiny.git

因为写这篇文章的时候RISC-V相关的代码已经合入,如果只是想了解一下的可以直接看arch/risc-v目录下的代码。如果想要体验亲手移植,就只保留kernel目录下的代码,其它部分直接删除。再新创建几个目录

mkdir -p risc-v/common/include
mkdir -p risc-v/rv32i
mkdir -p demo

最后目录效果如下

.
├── demo
├── kernel
│   ├── core
│   │   └── include
│   ├── evtdrv
│   │   └── include
│   ├── hal
│   │   └── include
│   └── pm
│       └── include
└── risc-v
    ├── common
    │   └── include
    └── rv32i

我们将在risc-v/common目录下填上RISC-V通用初始化代码,在risc-v/rv32i中填写risc-v 32位架构的移植代码,在demo中编写用户任务,启动内核。

这几项工作做完后,我们将上文提到的start.S和link.lds拷贝到demo目录下

在start.S代码里再添加一行call main

// start.S
.global _start
.extern main
.section .text
.align 2
_start:
    nop
    nop
    nop
    call main

再在该目录下新建个main.c

// demo/main.c
int main(void) {
    return 0;
}

最后在该目录下创建一个Makefile

TARGET = demo
DEBUG = 1
OPT = -O0

TOP_DIR = ../
BUILD_DIR = build

# C sources
KERNEL_SRC =  \
        C_SOURCES += $(KERNEL_SRC)

ARCH_SRC =  \
        ${wildcard $(TOP_DIR)/risc-v/rv32i/*.c} \
        ${wildcard $(TOP_DIR)/risc-v/common/*.c}
        C_SOURCES += $(ARCH_SRC)

HAL_DRIVER_SRC =  \
		$(TOP_DIR)/demo/main.c
        C_SOURCES += $(HAL_DRIVER_SRC)

# ASM sources
ASM_SOURCES =

ASM_SOURCES_S =  \
start.S


#######################################
# binaries
#######################################
PREFIX = riscv-none-embed-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S

#######################################
# CFLAGS
#######################################
# cpu
CPU = -march=rv32imac

# fpu
FPU =

# float-abi
FLOAT-ABI =

# mcu
MCU = $(CPU) $(FPU) $(FLOAT-ABI)

# macros for gcc
# AS defines
AS_DEFS =

# C defines
C_DEFS =

# AS includes
AS_INCLUDES =

# C includes
KERNEL_INC = \
        -I $(TOP_DIR)/risc-v/common/include \
        -I $(TOP_DIR)/risc-v/rv32i/
        C_INCLUDES += $(KERNEL_INC)

# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -mabi=ilp32 -msmall-data-limit=8 -mno-save-restore

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections -mabi=ilp32 -msmall-data-limit=8 -mno-save-restore -std=gnu11 --specs=nosys.specs -fmessage-length=0 -fsigned-char -ffunction-sections -fdata-sections

ifeq ($(DEBUG), 1)
CFLAGS += -g
endif


# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"


#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = link.lds

# libraries
LIBS = -lc -lm -lnosys
LIBDIR =
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections -nostartfiles

# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin


#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES_S:.S=.o)))
vpath %.S $(sort $(dir $(ASM_SOURCES_S)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@

$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@

$(BUILD_DIR):
	mkdir $@

#######################################
# clean up
#######################################
clean:
	-rm -fR $(BUILD_DIR)

#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

Makefile内容就不细讲了,相信各位都有能力看明白。

现在执行make,如果一切正常,就会在demo目录下生成build/demo.elf二进制文件。然后就可以开启qemu+gdb模拟调试,具体方法参见上文,后续不再赘述。

Breakpoint 1 at 0x80000000: file start.S, line 6.

Breakpoint 1, _start () at start.S:6
6	    nop
(gdb) disas
Dump of assembler code for function _start:
=> 0x80000000 <+0>:	nop
   0x80000002 <+2>:	nop
   0x80000004 <+4>:	nop
   0x80000006 <+6>:	jal	0x80000008 <main>
End of assembler dump.
(gdb) ni
7	    nop
(gdb)
8	    nop
(gdb)
9	    call main
(gdb) si
main () at main.c:1
1	int main(void ) {
(gdb) disas
Dump of assembler code for function main:
=> 0x80000008 <+0>:	addi	sp,sp,-16
   0x8000000a <+2>:	sw	s0,12(sp)
   0x8000000c <+4>:	addi	s0,sp,16
   0x8000000e <+6>:	li	a5,0
   0x80000010 <+8>:	mv	a0,a5
   0x80000012 <+10>:	lw	s0,12(sp)
   0x80000014 <+12>:	addi	sp,sp,16
   0x80000016 <+14>:	ret
End of assembler dump.
(gdb)

这个代码执行到sw s0,12(sp)这一句是会出问题的,因为我们并没有初始化栈的位置,所以引导程序需要重新设计一下。

我们先来确定一下引导程序start.S需要实现哪些功能:

禁止全局中断

我们的程序始终运行在机器模式下(下同),因此我们只需要关注mstatus寄存器的MIE位,参见《riscv-privileged.pdf》,具体内容如下:

因此禁止全局中断代码如下

csrc mstatus, 0x00000008

禁止时钟中断

时钟中断的禁用由mie寄存器的MTIE位控制

因此我们只需要将这位清0即可,因为其它位也用不到,所以直接简写如下

csrw mie, 0

指定中断处理服务入口

中为服务入口地址由mtvec寄存器指定

.extern trap_entry
la t0, trap_entry
csrw mtvec, t0

trap_entry的具体实现后续再讲,现在都还处在关中断阶段,暂时用不到。

链接脚本的改写

栈空间,全局数据,bss段的初始化离不开链接脚本的配合,因此我们重新编写链接脚本。

/* 指定架构 */
OUTPUT_ARCH( "riscv" )


/* 指定入口地址 */
ENTRY( _start )

MEMORY
{

    /* 参考qemu-4.1.0/hw/riscv/spike.c的spike_memmap相关代码 */
    ROM      (rxai!w)    : ORIGIN = 0x80000000, LENGTH = 512K
    RAM      (wxa!ri)    : ORIGIN = 0x84000000, LENGTH = 128K
}

SECTIONS
{
  .text : {
    PROVIDE( _text = . );
    *(.text.entry)          /* 将start.S的引导程序排在最前 */
    *(.text)
    *(.rodata)
    PROVIDE( _etext = . );
  } >ROM AT>ROM

  . = ALIGN(4);
  _load_data = LOADADDR(.data); /* data段的起始LMA */

  . = ALIGN(4);
  .data : {
    PROVIDE( _data = . ); /* data段的起始VMA */
    *(.data)
  . = ALIGN(4);
  } >RAM AT>ROM
  . = ALIGN(4);
  PROVIDE( _edata = . ); /* data段的终止VMA */

  . = ALIGN(0x1000);
  PROVIDE( _bss = . ); /* bss段的起始VMA */
  .bss : {
    *(.bss)
  } >RAM AT>RAM
  . = ALIGN(4);
  PROVIDE( _ebss = . ); /* bss段的终止VMA */


  . = ALIGN(8);
  PROVIDE( end = . );

  /* 初始栈空间为128个字节 */
  _stack_size = 128;
  .stack ORIGIN(RAM) + LENGTH(RAM) - _stack_size :
  {
    . = _stack_size;
    PROVIDE( _stack_top = . );
  } >RAM AT>RAM
  _end = .;
}

如果对链接脚本不熟悉,可以参考https://www.cnblogs.com/li-hao/p/4107964.html

栈、data段、bss段初始化

    la sp, _stack_top

    /* Load data section */
    la a0, _load_data
    la a1, _data
    la a2, _edata
    bgeu a1, a2, begin_clear_bss
clear_data:
    lw t0, (a0)
    sw t0, (a1)
    addi a0, a0, 4
    addi a1, a1, 4
    bltu a1, a2, clear_data

begin_clear_bss:
    // clear bss section
    la a0, _bss
    la a1, _ebss
    bgeu a0, a1, init_finish
clear_bss:
    sw zero, (a0)
    addi a0, a0, 4
    bltu a0, a1, clear_bss

经过整理,加上调用main函数的代码,可得完整的代码如下:

#define MSTATUS_MIE 0x00000008

.section .text.entry
    .globl _start
    .type _start,@function
    .extern trap_entry
_start:
    csrc mstatus, MSTATUS_MIE
    csrw mie, 0

    la t0, trap_entry
    csrw mtvec, t0

    la sp, _stack_top

    /* Load data section */
    la a0, _load_data
    la a1, _data
    la a2, _edata
    bgeu a1, a2, begin_clear_bss
clear_data:
    lw t0, (a0)
    sw t0, (a1)
    addi a0, a0, 4
    addi a1, a1, 4
    bltu a1, a2, clear_data

begin_clear_bss:
    // clear bss section
    la a0, _bss
    la a1, _ebss
    bgeu a0, a1, init_finish
clear_bss:
    sw zero, (a0)
    addi a0, a0, 4
    bltu a0, a1, clear_bss
init_finish:
    call main
__die:
    j __die

引导程序的主要工作已经基本完成,但现在编译还不能成功,链接器找不到trap_enry,因此我们暂时需要写个简单的函数,欺骗一下编译器。 在risc-v/rv32i/下创建一个port_s.S文件,编写如下代码

// port_s.S
.align 2
.global trap_entry
trap_entry:
    mret

在Makefile的ASM_SOURCES_S中加入port_s.S

ASM_SOURCES_S =  \
$(TOP_DIR)/risc-v/rv32i/port_s.S   \
start.S

然后再make生成二进制,启动qemu+gdb调试

Breakpoint 1, _start () at start.S:8
8	    csrc mstatus, MSTATUS_MIE
(gdb) b main
Breakpoint 2 at 0x80000078: file main.c, line 2.
(gdb) c
Continuing.

Breakpoint 2, main () at main.c:2
2	    return 0;
(gdb) disas
Dump of assembler code for function main:
   0x80000072 <+0>:	addi	sp,sp,-16
   0x80000074 <+2>:	sw	s0,12(sp)
   0x80000076 <+4>:	addi	s0,sp,16
=> 0x80000078 <+6>:	li	a5,0
   0x8000007a <+8>:	mv	a0,a5
   0x8000007c <+10>:	lw	s0,12(sp)
   0x8000007e <+12>:	addi	sp,sp,16
   0x80000080 <+14>:	ret
End of assembler dump.
(gdb) ni
3	}
(gdb)
0x8000007c	3	}
(gdb)
0x8000007e in main () at main.c:3
3	}
(gdb)
0x80000080 in main () at main.c:3
3	}
(gdb) disas
Dump of assembler code for function main:
   0x80000072 <+0>:	addi	sp,sp,-16
   0x80000074 <+2>:	sw	s0,12(sp)
   0x80000076 <+4>:	addi	s0,sp,16
   0x80000078 <+6>:	li	a5,0
   0x8000007a <+8>:	mv	a0,a5
   0x8000007c <+10>:	lw	s0,12(sp)
   0x8000007e <+12>:	addi	sp,sp,16
=> 0x80000080 <+14>:	ret
End of assembler dump.
(gdb)

可以看到已经能正常执行完main函数了。

现在我们的main函数是一个空函数,而一个正常的程序是需要在main函数里初始化内核并启动内核运行的,而这是有一个模板写法,因此我们把这个main函数重写一遍

// main.c
#include "tos.h"
int main(void) {
    // 内核初始化
    tos_knl_init()

    // 创建一些初始任务
    // 暂时用不到,先不写

    // 启动内核
    // 其实就是跳到某一个任务执行
    // 通过时钟中断高优先级抢占、或同优先级时间片轮转、或任务主动放弃CPU来切换任务执行
    tos_knl_start();

    // 以下代码,正常情况下永远执行不到
    while (1) {
        asm("wfi;");
    }
    return 0;
}

在成功编译这个还需要,添加一系列文件,这些文件基本都是程序化添加,主要是配置内核,另外就是实现一些接口。

添加tos_cpu_def.h

// risc-v/common/include/tos_cpu_def.h
#ifndef _TOS_CPU_DEF_H_
#define _TOS_CPU_DEF_H_

enum CPU_WORD_SIZE {
    CPU_WORD_SIZE_08,
    CPU_WORD_SIZE_16,
    CPU_WORD_SIZE_32,
    CPU_WORD_SIZE_64,
};

enum CPU_STK_GROWTH {
    CPU_STK_GROWTH_ASCENDING,
    CPU_STK_GROWTH_DESCENDING,
};

#endif /* _TOS_CPU_DEF_H_ */

添加tos_cpu_types.h

// risc-v/common/include/tos_cpu_types.h
#ifndef _TOS_CPU_TYPES_H_
#define _TOS_CPU_TYPES_H_

/* CPU address type based on address bus size.          */
#if     (TOS_CFG_CPU_ADDR_SIZE == CPU_WORD_SIZE_32)
typedef uint32_t    cpu_addr_t;
#elif   (TOS_CFG_CPU_ADDR_SIZE == CPU_WORD_SIZE_16)
typedef uint16_t    cpu_addr_t;
#else
typedef uint8_t     cpu_addr_t;
#endif

/* CPU data type based on data bus size.          */
#if     (TOS_CFG_CPU_DATA_SIZE == CPU_WORD_SIZE_32)
typedef uint32_t    cpu_data_t;
#elif   (TOS_CFG_CPU_DATA_SIZE == CPU_WORD_SIZE_16)
typedef uint16_t    cpu_data_t;
#else
typedef uint8_t     cpu_data_t;
#endif

#if     (TOS_CFG_CPU_HRTIMER_EN > 0)
#if     (TOS_CFG_CPU_HRTIMER_SIZE == CPU_WORD_SIZE_08)
typedef uint8_t     cpu_hrtimer_t;
#elif   (TOS_CFG_CPU_HRTIMER_SIZE == CPU_WORD_SIZE_16)
typedef uint16_t    cpu_hrtimer_t;
#elif   (TOS_CFG_CPU_HRTIMER_SIZE == CPU_WORD_SIZE_64)
typedef uint64_t    cpu_hrtimer_t;
#else
typedef uint32_t    cpu_hrtimer_t;
#endif
#else
typedef uint32_t    cpu_hrtimer_t;
#endif

//typedef cpu_addr_t  size_t;
typedef cpu_addr_t  cpu_cpsr_t;

#endif

添加tos_cpu.h

// risc-v/common/include/tos_cpu.h
#ifndef _TOS_CPU_H_
#define _TOS_CPU_H_

// 参考RISC-V的寄存器
// 用作任务栈初始化
// 保存恢复现场
typedef struct cpu_context_st {
    cpu_data_t          epc;
    cpu_data_t          mstatus;
    union { cpu_data_t  x1,  ra; };
    union { cpu_data_t  x3,  gp; };
    union { cpu_data_t  x4,  tp; };
    union { cpu_data_t  x5,  t0; };
    union { cpu_data_t  x6,  t1; };
    union { cpu_data_t  x7,  t2; };
    union { cpu_data_t  x8,  s0, fp; };
    union { cpu_data_t  x9,  s1; };
    union { cpu_data_t x10,  a0; };
    union { cpu_data_t x11,  a1; };
    union { cpu_data_t x12,  a2; };
    union { cpu_data_t x13,  a3; };
    union { cpu_data_t x14,  a4; };
    union { cpu_data_t x15,  a5; };
    union { cpu_data_t x16,  a6; };
    union { cpu_data_t x17,  a7; };
    union { cpu_data_t x18,  s2; };
    union { cpu_data_t x19,  s3; };
    union { cpu_data_t x20,  s4; };
    union { cpu_data_t x21,  s5; };
    union { cpu_data_t x22,  s6; };
    union { cpu_data_t x23,  s7; };
    union { cpu_data_t x24,  s8; };
    union { cpu_data_t x25,  s9; };
    union { cpu_data_t x26, s10; };
    union { cpu_data_t x27, s11; };
    union { cpu_data_t x28,  t3; };
    union { cpu_data_t x29,  t4; };
    union { cpu_data_t x30,  t5; };
    union { cpu_data_t x31,  t6; };
} cpu_context_t;

// 计算一个整数的二进制的前导0个数
// 这个函数应该放在内核里,不应该放到移植层来实现,不过当前暂时不能改变
// 所以先勉为其难地实现一个
__API__ uint32_t        tos_cpu_clz(uint32_t val);

// 开关中断,应该没有用到,可以先不实现
__API__ void            tos_cpu_int_disable(void);

__API__ void            tos_cpu_int_enable(void);

// current program status register
// 当前程序状态的保存和恢复
// 这个很重要,必需要实现
__API__ cpu_cpsr_t      tos_cpu_cpsr_save(void);

__API__ void            tos_cpu_cpsr_restore(cpu_cpsr_t cpsr);

__KERNEL__ void         cpu_init(void);

// 配置时钟中断
__KERNEL__ void         cpu_systick_init(k_cycle_t cycle_per_tick);

// 跳到第一个任务
// 如果事先没有创建任务,则跳到内核自带的idle任务执行
__KERNEL__ void         cpu_sched_start(void);

// 任务切换
__KERNEL__ void         cpu_context_switch(void);

__KERNEL__ void         cpu_irq_context_switch(void);

// 任务栈初始化
__KERNEL__ k_stack_t   *cpu_task_stk_init(void *entry,
                                                          void *arg,
                                                          void *exit,
                                                          k_stack_t *stk_base,
                                                          size_t stk_size);
#endif // _TOS_CPU_H

添加tos_fault.h

// risc-v/common/include/tos_fault.h
#ifndef _TOS_FAULT_H_
#define _TOS_FAULT_H_

#if TOS_CFG_FAULT_BACKTRACE_EN > 0u

#error "unsupport now"

#endif

#endif /* _TOS_FAULT_H_ */

添加port_config.h

// risc-v/rv32i/
#ifndef _PORT_CONFIG_H_
#define _PORT_CONFIG_H_

#define TOS_CFG_CPU_ADDR_SIZE               CPU_WORD_SIZE_32
#define TOS_CFG_CPU_DATA_SIZE               CPU_WORD_SIZE_32
#define TOS_CFG_CPU_STK_GROWTH              CPU_STK_GROWTH_DESCENDING
#define TOS_CFG_CPU_HRTIMER_EN              0u
#define TOS_CFG_CPU_LEAD_ZEROS_ASM_PRESENT  0u

#endif /* _PORT_CONFIG_H_ */

添加port.h

// risc-v/rv32i/port.h
#ifndef _PORT_H_
#define _PORT_H_

__PORT__ void       port_int_disable(void);

__PORT__ void       port_int_enable(void);

__PORT__ cpu_cpsr_t port_cpsr_save(void);

__PORT__ void       port_cpsr_restore(cpu_cpsr_t cpsr);

__PORT__ void       port_cpu_reset(void);

__PORT__ void       port_sched_start(void) __NO_RETURN__;

__PORT__ void       port_context_switch(void);

__PORT__ void       port_irq_context_switch(void);

__PORT__ void       port_systick_config(uint32_t cycle_per_tick);

__PORT__ void       port_systick_priority_set(uint32_t prio);

#endif /* _PORT_H_ */

添加tos_config.h

// demo/tos_config.h

#ifndef INC_TOS_CONFIG_H_
#define INC_TOS_CONFIG_H_

#include "stddef.h"

#define TOS_CFG_TASK_PRIO_MAX 10u // 配置TencentOS tiny默认支持的最大优先级数量

#define TOS_CFG_ROUND_ROBIN_EN 0u // 配置TencentOS tiny的内核是否开启时间片轮转

#define TOS_CFG_OBJECT_VERIFY 0u // 配置TencentOS tiny是否校验指针合法

#define TOS_CFG_EVENT_EN 1u // TencentOS tiny 事件模块功能宏

#define TOS_CFG_MMHEAP_EN 1u // 配置TencentOS tiny是否开启动态内存模块

#define TOS_CFG_MMHEAP_POOL_SIZE 8192 // 配置TencentOS tiny动态内存池大小

#define TOS_CFG_MUTEX_EN 1u // 配置TencentOS tiny是否开启互斥锁模块

#define TOS_CFG_QUEUE_EN 1u // 配置TencentOS tiny是否开启队列模块

#define TOS_CFG_TIMER_EN 0u // 配置TencentOS tiny是否开启软件定时器模块

#define TOS_CFG_SEM_EN 1u // 配置TencentOS tiny是否开启信号量模块

#if (TOS_CFG_QUEUE_EN > 0u)
#define TOS_CFG_MSG_EN 1u
#else
#define TOS_CFG_MSG_EN 0u
#endif

#define TOS_CFG_MSG_POOL_SIZE 10u // 配置TencentOS tiny消息队列大小

#define TOS_CFG_IDLE_TASK_STK_SIZE 512u // 配置TencentOS tiny空闲任务栈大小

#define TOS_CFG_CPU_TICK_PER_SECOND 1000u // 配置TencentOS tiny的tick频率

#define TOS_CFG_CPU_CLOCK 108000000 // 配置TencentOS tiny CPU频率

#define TOS_CFG_TIMER_AS_PROC 1u // 配置是否将TIMER配置成函数模式

#define TOS_CFG_VFS_EN 1u

#define TOS_CFG_MMBLK_EN 1u


#endif /* INC_TOS_CONFIG_H_ */

为了编译通过,我们暂时添加tos_cpu.c

// risc-v/common/tos_cpu.c
#include 
__API__ uint32_t        tos_cpu_clz(uint32_t val){
    return 0;
}

__API__ void            tos_cpu_int_disable(void){
}

__API__ void            tos_cpu_int_enable(void){
}

__API__ cpu_cpsr_t      tos_cpu_cpsr_save(void){
    return 0;
}

__API__ void            tos_cpu_cpsr_restore(cpu_cpsr_t cpsr){
}

__KERNEL__ void         cpu_init(void){
}

__KERNEL__ void         cpu_systick_init(k_cycle_t cycle_per_tick){
}

__KERNEL__ void         cpu_sched_start(void){
}

__KERNEL__ void         cpu_context_switch(void){
}

__KERNEL__ void         cpu_irq_context_switch(void){
}

__KERNEL__ k_stack_t   *cpu_task_stk_init(void *entry,
                                                          void *arg,
                                                          void *exit,
                                                          k_stack_t *stk_base,
                                                          size_t stk_size){
    return 0;
}

修改Makefile

KERNEL_SRC =  \
        ${wildcard $(TOP_DIR)/kernel/core/*.c}
        C_SOURCES += $(KERNEL_SRC)

# C includes
KERNEL_INC = \
        -I $(TOP_DIR)/kernel/core/include  \
        -I $(TOP_DIR)/kernel/pm/include  \
        -I $(TOP_DIR)/kernel/hal/include \
        -I $(TOP_DIR)/risc-v/common/include \
        -I $(TOP_DIR)/risc-v/rv32i/ \
        -I $(TOP_DIR)/demo
        C_INCLUDES += $(KERNEL_INC)

至此,整体框架已经搭建完毕,下一章就可以进行实际的移植代码编写。