嵌入式开发是一种和硬件结合紧密的开发方式。是一种比较综合的技术:在特定的硬件环境下针对特定的某款硬件进行开发。我们的操作系统就会涉及到很多嵌入式开发的内容。
参与编译和运行的程序可以分为三类:
build系统:生成编译器可执行程序。(的计算机系统)
host系统:运行编译器程序生成应用程序。(的计算机系统)
target系统:运行生成的应用程序。(的计算机系统)
本地(native)编译:三者相同。build==host==target系统
交叉(cross)编译:三者相同。build==host!=target系统
我们使用的GCC编译器可以看到最终指向:usr/bin/x86_64-linux-gnu-gcc-9
所以这一编译程序是用于编译在x86架构linux上的程序的。
我们需要的是riscV64-unknown-elf-gcc,这样输出的a.out文件才能在riscV上运行。注意,这个编译器是需要我们自己做的,当然这里目的是开发操作系统而不是编译器直接用现成的即可。
这里是为了了解交叉编译的概念。
GNU project Debugger。远程调试和本地调试。这个程序用于查看另一个程序在执行过程中正在执行的操作。远程调试是程序并不在本地运行,可能需要目标机一个gdb sever 来与调试机交互。这里我们开发过程中调试机是用模拟器模拟出来的环境。
GDB基本调试流程可以参考网上资料,不在这里赘述。
quick emulator:计算机模拟软件。支持多种架构。
有两种运行模式:
用户模式:仅运行用户程序。
系统模式:模拟整个计算机系统。
那么我们开始简易的尝试。事实上我在这里卡了大约6个小时左右。因为在编译hello.c文件时报了错误(找不到头文件)。我查询了解决办法,如果有小伙伴在这和我遇到了同样的麻烦,我下面会给出几个解决办法。
这里我们如果用gcc编译就会编译出在本地运行的程序,明显这不是我们想要的。
我们使用riskv64-unknown-elf-gcc
我们这里会报错。也就是:
这个错误是由于我们使用的这个编译器不自带任何头文件导致的,我给出三个解决办法。
1.给该编译器搜索头文件的路径加上你所需的头文件。也就是缺啥补啥。
2.更换你所使用的编译器,也就是安装新的编译器。
3.回到一开始配置环境哪里去下载配置好的tool文件解压。
这里我是用第二种方法的,毕竟我们是开发一个从零开始的操作系统,本来就不使用库文件。
可以看到编译成功了。这里是无法执行的,上面说过了这是编译到riscv架构使用的应用程序。当然了我们调试可以携带参数。
我这里是报了错的,也就是说,使用riscv64-linux-gnu-gcc不能携带参数,事实上也是缺少文件导致的。
这里我们暂时不过多操作,因为编写带有头文件的程序不是我们的目的。 有兴趣的朋友自己去试着解决。
这能看到a.out的文件信息,64位等等。
qemu-riscv32 a.out
提示你没有安装就按照他的安装一个用户模式即可。sudo apt install qemu-user
当然了如果你是用的我的方式最初apt配置的环境,这一节的实验都难以完成。当然了,对后续我们学习汇编没有什么太大影响,之前apt安装主要是为了小白图方便省时间。这也算是弊端之一吧。
模拟器就到这里,后续我会使用ubuntu18。04下载包再来配置的方式来进行实践。如果不要求实践一模一样可以继续使用ubuntu20.04。或者安装20.04的工具包。
1804与2004的工具包
注意较小的那个为18.04ubuntu的工具包。
解压之后共享文件夹再复制到你的工作空间解压,注意tools文件夹下就是gcc和qemu文件夹了。下面的步骤为开发环境配置的补充与理论学习无关。
18.04版本ubuntu我也给大家尝试过了。也是可以的。
然后将以下路径加入 $HOME/.bashrc
:
1.修改文件
gedit $HOME/.bashrc
再把下面的复制粘贴到最后
export PATH="$HOME/ws/tools/gcc/bin:$HOME/ws/tools/qemu/bin:$PATH"
这就成功了。
注意这些并非我们开发必备,这只是给我们配置环境的一种选择。18.04和20.04我都给出了工具包下载。过程大致相同。都是:
下载工具包解压放在指定位置
把工具包gcc与qemu路径添加入path。
source $HOME/.bashrc
好,qemu具有模拟功能咱们就讲到这里。
如果你只有一个文件需要编译事实上用不上make,但是如果你的文件比较多,就需要你使用make自动化地来管理你的编译过程。那么对于具体的操作就是通过makefile文件来管理。
make如何找到makefile,默认是在当前工作目录下找,也可也指定路径,咱们不多谈。
makefile由几条规则组成。每条规则内容:
例子:注意command前有tab(类似python缩进)
target:prerequisites
command
hello:hello.c
gcc hello.c -o hello
还有一些特殊的内容:
缺省规则:
.DEFALT_GOAL:= all
all :
伪规则:
.PHONY: clean
clean:
rm -f *.o
注释:
#注释,行注释
运行时默认是执行上面的规则(靠前的作为终极规则)。默认规则就可以作为一个特例,放在后面也没关系。伪规则相当于有一些动作,但是不会参与规则推导。
这是一个makefile文件,了解的同学可以自己看一下。其他同学咱们可以通过其他渠道简单学习一下。
include ../../common.mk
SRCS_ASM = \
start.S \
SRCS_C = \
kernel.c \
OBJS = $(SRCS_ASM:.S=.o)
OBJS += $(SRCS_C:.c=.o)
.DEFAULT_GOAL := all
all: os.elf
# start.o must be the first in dependency!
os.elf: ${OBJS}
${CC} ${CFLAGS} -Ttext=0x80000000 -o os.elf $^
${OBJCOPY} -O binary os.elf os.bin
%.o : %.c
${CC} ${CFLAGS} -c -o $@ $<
%.o : %.S
${CC} ${CFLAGS} -c -o $@ $<
run: all
@${QEMU} -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
@echo "------------------------------------"
@${QEMU} ${QFLAGS} -kernel os.elf
.PHONY : debug
debug: all
@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
.PHONY : code
code: all
@${OBJDUMP} -S os.elf | less
.PHONY : clean
clean:
rm -rf *.o *.bin *.elf
那么就是希望大家知道我们要开发内核要有哪些知识。每一个部分做什么,有什么作用。
下一节就讲汇编了,希望大家不要放弃。