SkyEye仿真平台下的操作系统实验- 准备篇(一)
《一步步写嵌入式操作系统》从零开始实现了操作系统leeos,并在SkyEye虚拟机中进行了实验验证。
leeos是一个简单的多任务操作系统,进程调度方式为时间片轮询。leeos使用伙伴算法实现了动态内存分配,支持romfs文件系统,支持运行elf格式的可执行文件。leeos提供了一个简单的驱动程序框架,和一个统一的异常与中断处理架构,并通过使用中断嵌套的方法提高了中断效率。leeos支持mmu,使用两级页表结构实现虚拟地址到物理地址的转换。以上涉及到的技术都在《一步步写嵌入式操作系统》中有详细讲解。
SkyEye,中文全称天目全数字实时仿真软件,是基于可视化建模的硬件行为级仿真平台,支持用户通过拖拽的方式对硬件进行行为级别的仿真和建模。
SkyEye目前支持主流的嵌入式硬件平台,可以运行主流的操作系统,此外还能适配国内自主研发的操作系统天脉。通过利用基于LLVM的动态二进制翻译技术,使虚拟处理器在典型的桌面计算机上运行速度可以达到2000MIPS以上。
使用SkyEye仿真平台可以快速搭建leeos操作系统的硬件环境,还可以从搭建的过程中了解到硬件系统的总体架构。操作系统与硬件之间是紧密交互的关系,学习在SkyEye仿真平台中搭建硬件环境和编写其上的操作系统,有助于更深入的了解操作系统运行的原理。
本系列文章将分为两个部分:“准备篇”和“实验篇”。“准备篇”用来阐述《一步步写嵌入式操作系统》在实现leeos操作系统过程中遇到的一些操作系统概念和编程知识。“实验篇”用来复现原书在SkyEye仿真环境下运行leeos的实验。
01
helloworld!
现在编写一个简单的串口测试代码,并在SkyEye中运行起来。
#define UFCON0 ((volatile unsigned int *)(0x50000020)) void helloworld(void){ const char *p="helloworld!\n"; while(*p){ *UFCON0=*p++; }; while(1); }
此代码假设串口FIFO寄存器地址为0x50000020,通过向此地址依次写入字符最终会在串口中打印出字符串"helloworld!\n"。
将上述代码保存为helloworld.c,并使用”arm-elf-gcc -O2 -c helloworld.c” 会生成”helloworld.o”文件。使用”arm-elf-ld -e helloworld -Ttext 0x0 helloworld.o -o helloworld”指定生成的helloworld可执行文件的入口为helloworld函数,此函数代替了main函数。在使用”arm-elf-objcopy -O binary helloworld helloworld.bin”将可执行elf文件所包含的机器码抽离出来生成”helloworld.bin”文件。
配置SkyEye启动文件”helloworld.skyeye”,在此配置文件中会指定运行的arm核心、需要在硬件仿真环境中运行的bin文件路径、bin文件的烧写地址等信息。再通过图形建模等工具生成一个硬件描述文件”helloworld.json”,此文件中描述了硬件的建模信息,如内存、处理器、中断控制器、定时器、串口等大小型号。也可以直接导入受支持的某个开发板的硬件描述json文件。最后将helloworld.bin、helloworld.json和helloworld.skyeye文件放在一个目录中,启动天目.exe,打开启动文件,即可在term中显示出”helloworld!\n”。SkyEye会将串口输出映射到term中显示。
上述实验是一个简单的过程描述,具体实验部分可见后续的《一步步写嵌入式操作系统》-实验篇。
02
链接脚本
通常我们使用gcc编译程序时并没有使用链接脚本,这是因为arm-elf-ld链接工具会使用内嵌到链接工具内部的默认脚本,可使用-verbose参数来查看。
当应用程序运行在操作系统之上时,往往不需要显式指定链接脚本,因为自己写的链接脚本可能与操作系统默认环境不符,导致运行出错。但是如果程序运行于操作系统之下,或者就是操作系统本身,编写一个链接脚本就显得十分重要了。下面是一个链接脚本的例子:
ENTRY(helloworld) SECTIONS{ . = 0x00000000; .text :{ *(.text) } . = ALIGN(32); .data :{ *(.data) } . = ALIGN(32); .bss :{ *(.bss) } }
此链接脚本使用了一个非常重要的命令-SECTIONS,用来描述输出文件的内存布局,定义了程序各段的输出位置。
在程序链接时使用-T参数即可使用连接脚本,例如:
arm-elf-ld -T helloworld.lds helloworld.o -o helloworld。
03
Arm汇编语言
arm寄存器有参与普通运算的r0-r12,保存堆栈地址的r13,保存程序返回值的r14,记录程序地址待r15。r13、r14、r15有对应的别名SP、LR、PC。还有一个保存程序运行状态的特殊寄存器CPSR。
在实际应用中操作系统是由汇编和c语言编写的。下面是一个arm汇编程序的例子:
.arch armv4 .global helloworld .equ REG_FIFO, 0x50000020 .text .align 2 helloworld: ldr r1,=REG_FIFO adr r0,.L0 .L2: ldrb r2,[r0], #0x1 str r2,[r1] cmp r2,#0x0 bne .L2 .L1: b .L1 .align 2 .L0: .ascii "2.3_helloworld\n\0"
上面的汇编程序涉及到了一些伪指令,其含义如下:
.arch:选择程序运行的体系架构。
.global:声明某个符号对链接器可见,例如上面的.global helloworld会声明helloworld函数全局可见。
.equ:相当于C语言中的宏定义。
.text:表示接下来的代码会归并到代码段中。
.align:代码对齐。与sparc和X86等架构不同的是,arm的.align后面的数字已幂的形式出现。例如.align 4,在sparc、X86架构中代表4字节对齐,而在arm中就是16字节对齐。
.ascii:用于在内存中定义字符串。
ldr、adr:常量装载、地址装载其实也是伪指令。
04
汇编和C语言混合编程
很少有操作系统会完全用汇编实现,绝大部分是汇编和C语言一起写成的。
要具体实现汇编语言和C语言之间的混合编程,就必须制订一套统一的标准,我们将这套共同遵守的标准称为过程调用标准(Procedure Call Standard),简称PCS。
ARM的PCS有很多版本,如Thumb过程调用标准TPCS、ARM过程调用标准APCS,ARM-Thumb之间相互调用是的过程调用标准ATPCS。目前最新的一套过程调用标准名为AAPCS(Procedure Call Standard for the ARM Architecture)。
例:
printf.arch armv4.global _start.equ REG_FIFO, 0x50000020.text.align 2_start: ldr r0,=REG_FIFO adr r1,.L0 bl helloworld("hello world!");
上例中使用bl helloworld指令实现了汇编调用c语言的过程。在bl之前向r0和r1赋值,从而向helloworld函数传参。
以上涉及到的操作系统概念与嵌入式技术在《一步步写嵌入式操作系统》中有更详细的讲解,推荐阅读。