本章节,我们将使用qemu模拟一个cortex-a57的设备,并在上边输出一个hello类型的输出。开始之前,先了解一下AArch64的一些相关知识。更详细的内容可参照arm发布的手册。
AArch64的异常等级
在ARMV8的体系结构里,可执行程序都运行在一个特定的异常等级上。不同的异常等级有不同的权限。
异常等级 | 运行的典型程序 |
---|---|
EL0 | 普通的用户应用程序(APP) |
EL1 | 操作系统的内核(Kernel) |
EL2 | 虚拟化管理程序(Hypervisor.) |
EL3 | 安全监视程序(Secure monitor) |
AArch64的寄存器
AArch64的寄存器分为以下几种:
1)通用寄存器
包含了31个64位通用寄存器X0~X30,其中X29也被称为FP,X30也被称为LR
W0~W30则分别表示X0~X30的低32位。(可能是为了兼容AArch32)
2)特殊寄存器
XZR--这个寄存器的值一直是0,通常用于清零或者和0进行比较。
PC--程序计数器。
SP--栈指针寄存器,每个特权等级有单独的,方便进行等级切换。由PSTATE寄存器中的SP位决定使用哪个等级的SP.
ELR--异常链接寄存器,指示产生异常的地址。EL0的异常会陷入到内核中,因此没有EL0级别的ELR
3)系统寄存器
包含页表基地址寄存器、控制寄存器、定时器计数寄存器等等。这里不一一介绍,用到的时候再说。
构建基础工程
代码目录如下:
.
├── kernel //内核相关的代码
│ ├── arch //硬件架构相关代码
│ │ ├── aarch64 //aarch64相关的代码
│ │ │ └── boot.s //aarch64启动阶段的代码
│ │ ├── CMakeLists.txt
│ │ └── include //通用的头文件位置
│ │ ├── mem.h
│ │ └── reg_op.h
│ ├── CMakeLists.txt
│ └── include //kernel总的头文件位置
│ └── type.h
└── vendor //板子相关的代码
├── board
│ ├── CMakelists.txt
│ ├── include //通用头文件
│ │ └── mem_addr.h
│ ├── main.c //函数入口
│ └── qemu_a57 //板级---qemu_a57相关的内容
│ ├── include
│ │ └── mm.h //内存地址的分布
│ └── uart_pl011.c //pl011的串口驱动
├── build_hfos.sh //构建工程的脚本
├── cmake //cmake文件存放处
│ ├── link.ld
│ └── qemu_a57.cmake //qemu_a57编译用的cmake文件
├── CMakeLists.txt
├── env_setup //编译用的环境变量
├── include //通用头文件的定义,不同板子需要实现
│ └── uart.h
└── run_hfos.sh //编译完成后运行仿真的脚本
代码
section ".text.boot"
.globl _start
_start:
mrs x0, mpidr_el1 //获取cpu num MPIDR_ELn Multiprocessor Affinity Register
and x0, x0,#0xFF
cbz x0, main_core //等于0,跳转到main_core标签
b slave_core //不等于0,跳转到无限循环
//启动主核
main_core:
//初始化BSS段
adr x0, bss_begin
adr x1, bss_end
cmp x0, x1 //比较是否相等
b.eq set_sp //相等则不进行清零
meminit:
str xzr, [x0], #8 //进行清零操作
cmp x0, x1
b.ne meminit //循环执行
//给main函数设置栈地址,跳转到主函数运行
set_sp:
mov sp, 0x60000000 //设置到一个有效的物理内存地址
bl main
b dead_loop
//死循环
dead_loop:
b dead_loop
//不启动从核,让他自旋
slave_core:
b dead_loop
main.c
#include "mem_addr.h"
#include "uart.h"
void delay(int cnt)
{
while (cnt--) {
;
}
}
void main(void)
{
uart_init(); //初始化一个串口,然后打印一些信息
uart_send_string(" _ __ ____ _____ \n");
uart_send_string(" | | / _|/ __ \\ / ____|\n");
uart_send_string(" | |__ | |_| | | | (___ \n");
uart_send_string(" | '_ \\| _| | | |\\___ \\ \n");
uart_send_string(" | | | | | | |__| |____) |\n");
uart_send_string(" |_| |_|_| \\____/|_____/ \n");
uart_send_string("Start with Hello world!\n");
delay(1000000000);
uart_send_string("Tomorrow will be better!\n");
delay(1000000000);
uart_send_string("hfOS go! go! go!\n");
while (1) {
;
}
}
其他信息
我们在mm.h中定义了如下的地址信息,并添加了一个pl011的串口驱动,那是如何确定串口芯片,内存地址等信息呢?
我们在run_hfOS.sh中的运行命令为
qemu-system-aarch64 -m 1024 -machine virt,gic-version=3,virtualization=on -cpu cortex-a57 -smp 4 -nographic -kernel ../build/out/bin/hfOS_qemu_a57.elf
qemu会根据我们的命令生成对应的设备树,我们把设备树信息导出来就可以看到内存地址,驱动芯片等相关信息了。
调用如下的指令将此次使用的dtb文件导出来
qemu-system-aarch64 -m 1024 -machine virt,dumpdtb=dump.dtb,gic-version=3,virtualization=on -cpu cortex-a57 -smp 4 -nographic
然后使用dtc工具将dtb转换为dts文件
dtc -I dtb -O dts -o dump.dts dump.dtb
打开后能看到如下信息,确定内存的起始位置是0x40000000,PL011的基地址是0x9000000
手册:
代码编译和运行
git clone https://gitee.com/genglufei/hfos.git
cd hfOS/day1_boot/hfOS/vendor
./build_hfos.sh qemu_a57
./run_hfos.sh
运行结果如下:
下一章节我们将尝试为OS添加一个任务,并运行起来。