跟着[完结] 循序渐进,学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春视频走完了开发一个简易操作系统的全流程。从中学到了一个简易操作系统启动的顺序、如何编写汇编启动文件、ld链接文件的作用以及如何指导程序在mcu的ram中的布局,以及如何使用通用寄存器、外设寄存器、状态控制寄存器等。
在第 0 章 Bootstrap 中,我们需要深入理解 Makefile
的工作原理,确保能够准确解析并理解 Makefile
文件中每一行指令的具体作用。同时,还需掌握开发板 MCU 如何定位程序的入口地址的机制,以便更好地理解系统初始化过程和启动流程。
一、Makefile的工作机制
Makefile是一种自动化构建工具,用于管理大型项目中的编译和构建过程。
Makefile的基本结构:
Makefile是由一系列的规则、变量和指令组成。其基本结构如下:
%.o : %.c ${CC} ${CFLAGS} -c -o $@ $<
其中%.o为目标任务,%.c为依赖文件,其余为执行的命令。
变量的定义和使用:
Makefile允许定义变量,例如编译器和编译选项:
COMPILE_PREFIX ?= riscv-nuclei-elf- CC := $(COMPILE_PREFIX)gcc CFLAGS = -nostdlib -fno-builtin -march=rv32imac -mabi=ilp32 -g -Wall -fno-common -DDOWNLOAD_MODE=DOWNLOAD_MODE_FLASHXIP
上述的${CC}和${CFLAGS}就是事先定义好的变量,分别表示编译器和编译选项。
riscv-nuclei-elf-gcc就表示针对芯来的交叉编译工具链。 而CFLAGS中就是一些编译选项,比如-nostdlib就是禁用链接标准库,-march就是在指定目标架构和使用的指令集。
Makefile中的自动化变量:自动化变量是make工具提供的特殊变量,自动根据当前的规则和目标替换为相应的值。比如几个常见的自动化变量:
$@: 代表当前规则的目标文件名
$<: 代表当前规则中的第一个依赖文件名
$^: 代表当前规则中的所有依赖文件名。
简而言之,Makefile 用于自动化管理多个源文件的编译过程。首先,它通过编译器将多个 .c
源文件编译为 .o
目标文件,在这个过程中,源文件中的 #include
头文件会被包含并处理。接着,Makefile 使用链接器将多个 .o
文件链接成一个可执行文件(通常是 .elf
格式)。最后,生成的 .elf
文件会被转换为 .bin
格式,以便烧录到开发板中进行运行。
从.c 到 .o:文件的转换通常包括如下步骤:
1、预处理。编译器首先处理源文件中的宏定义、头文件引用等。结果是一个没有宏和文件包含的纯代码文件。通常为.i文件。
2、编译。编译器将预处理后的文件编译成汇编语言代码,生成.s文件。
3、汇编。汇编器将汇编语言转换成机器码,生成.o文件,包含了符号表、调试信息、机器码。
从若干.o 到 .elf:主要是将多个.o文件链接到一起,生成一个可执行的ELF文件。
1、链接。链接器会将多个.o文件和库文件合并成一个单一的可执行文件。这个过程包括:符号解析、重定位、地址分配。
2、生成ELF文件。完成链接后,链接器将所有内容打包为一个ELF文件。这个ELF文件包含以下部分:头部、代码段、数据段、符号表和重定位信息。
二、MCU的启动汇编start.S
MCU是如何找到程序的第一条执行语句在哪里的呢?通常由链接脚本 .ld 文件通过:
ENTRY( _start )语句指出MCU复位后程序的入口地址。不过在第0章bootstrap和第1章HelloRVOS中并没有引入 .ld 链接文件。 因此此处通过编译参数 -Ttext=20000000指明复位后的程序入口地址。结合start.S汇编启动文件中的.text字段指明在地址0x20000000处从哪一行代码开始存放,共同指导了MCU复位的启动过程。
三、烧录与调试
OpenOCD是一个开源调试器,支持通过JTAG或SWD等接口与目标硬件进行通信,以便进行程序调试、烧录固件。是一个由社区驱动的开源项目,通常公司会提供基于OpenOCD的定制版本。
与OpenOCD配合使用的是 .cfg 文件。.cfg文件同时指定了调试的接口类型、目标硬件类型、调试器连接参数等。OpenOCD需要.cfg文件来正确识别调试硬件、目标芯片及其相关设置。
通过以下命令烧录固件:
${OPENOCD} -f ${CFG} -c init -c halt -c "flash write_image os.bin 0x20000000" -c reset -c shutdown
通过以下命令通过OpenOCD连接目标板,并通过gdb开始调试:
run_openocd:
$(OPENOCD) -f ${CFG}
run_gdb:
@$(ECHO) "Run gdb to connect openocd server and debug"
$(GDB) os.elf $(GDB_CMDS)