使用 QEMU 进行嵌入式系统开发 第2部分 (译)

  2014-02-17 00:00:00 CST

  Jingwen Peng

  Linux

Chipset

本文翻译自 Linux for You 网站上的文章 Using QEMU for Embedded Systems Development, Part 2 (2011.7)

正文如下


在前几篇文章,我们学习了 如何使用 QEMU 安装 Linux 操作系统,使用 OpenVPN 和 TAP/TUN 为 QEMU 配置网络,为 QEMU 编译 ARM Linux 内核,使用 QEMU 启动内核,以及创建一个小型的文件系统并挂载到内核上。现在我们继续进一步的探索。

首先,我想解释一下为什么需要 Bootloader。Bootloader 是可以把内核加载到内存中的代码,并且指定挂载哪个分区作为根文件系统。Bootloader 保存在 MBR (主引导记录)。在通用计算机中存在一个叫 BIOS (基本输入输出系统) 的重要组件,BIOS 包含着如键盘、鼠标、显示器等设备的低级驱动。它可以用来初始化 Bootloader,然后 Bootloader 引导内核。Linux 用户应该对 GRUB (Grand Unified Boot-Loader) 和 LILO (Linux Loader) 并不陌生。

单片机程序员应该对 “裸机开发” 这个术语很熟悉,它意味着你的程序直接接触处理器,也就是说你写的代码直接运行在处理器上。这样一来,程序员必须考虑到每一种可能使系统崩溃的情况。

现在,让我们试着为 ARM Versatile 平台开发板写一个小程序,它将运行在 QEMU 模拟器上,并在串口控制台上输出一段信息。下载 ARM EABI 的工具链,解压并加入到 PATH。

默认情况下,QEMU 将串口控制台的输出重定向到终端,但是可以使用 ‘nographic’ 选项:

1
2
$  qemu-system-arm --help | grep nographic
-nographic disable graphical output and redirect serial I/Os to console. When using -nographic, press 'ctrl-a h' to get some help.

我们可以很好的利用这个功能,现在我们可以试着向串口写数据。

在更进一步之前,我们必须确认 GNU EABI 工具链支持什么样的处理器,QEMU 支持什么样的处理器,两个支持的处理器应该是相似的。先检查 QEMU,之前我们源码编译过 QEMU,就用这个源码来看看它支持什么样的 ARM 处理器:

1
2
3
4
5
$ cd (your-path)/qemu/qemu-0.14.0/hw
$ grep "arm" versatilepb.c
#include "arm-misc.h"
static struct arm_boot_info versatile_binfo;
cpu_model = "arm926";

很明显,QEMU 支持 “arm926” 处理器。下面检查 GNU ARM 工具链:

1
2
3
4
5
$ cat gcc.info | grep arm | head -n 20
'strongarm1110', 'arm8', 'arm810', 'arm9', 'arm9e', 'arm920',
'arm920t', 'arm922t', 'arm946e-s', 'arm966e-s', 'arm968e-s',
'arm926ej-s', 'arm940t', 'arm9tdmi', 'arm10tdmi', 'arm1020t',
'arm1026ej-s', 'arm10e', 'arm1020e', 'arm1022e', 'arm1136j-s',

没问题。GNU ARM 工具链支持 ARM926EJ-S 处理器。现在我们向该处理器的串口写数据。因为我们没有使用描述 UART0 地址的头文件,我们必须手动指定,’(your-path)/qemu/qemu-0.14.0/hw/versatilepb.c’:

1
2
3
4
/* 0x101f0000 Smart card 0. */
/* 0x101f1000 UART0. */
/* 0x101f2000 UART1. */
/* 0x101f3000 UART2. */

开源代码很强大,他清晰给你了每一个设备地址。UART0 在地址 ‘0x101f1000’。为了测试,我们直接对这个地址写数据,然后检查终端。

我们的第一个测试程序是直接运行在处理器上的裸机程序,没有 Bootloader 的帮助,我们需要创建三个重要的文件。首先,是一个小程序 (‘init.c’):

1
2
3
4
5
6
7
8
9
10
11
12
volatile unsigned char * const UART0_PTR = (unsigned char *)0x0101f1000;

void display(const char *string){
	while (*string != '\0'){
		*UART0_PTR = *string;
		string++;
	}
}

int my_init(){
	display("Hello Open World\n");
}

我们看看这段程序做了什么。 首先,我们声明了一个 volatile 指针变量,然后赋值为串口号 (UART0) 。函数 ‘my_init()’,是主流程。它仅仅调用了函数 ‘display()’,向 UARTO 串口写入了一个字符串。

这对于底层单片机程序员应该很简单。但是如果你没有多少嵌入式开发经验,你可能会被搞晕。微处理器是一个集成芯片,带有不同的输入输出端口等等。 ARM926EJ-S 有四个串口 (从它的配置清单得知);他们有他们的数据线 (地址)。当处理器被编程向一个串口输出,它会向串口地址写数据。这就是程序做了什么。

下一步为处理器开发启动代码,当处理器上电时,会跳到特定地址,从那个地址读取代码并执行。即使是重置之后 (就像桌面计算机重置一样),处理器也会跳到预定地址执行。下面是启动代码,’startup.s’:

1
2
3
4
5
.global _Start
_Start:
LDR sp, = sp_top
BL my_init
B .

第一行,’_Start’ 声明为全局。下一行是 ‘_Start’ 的起始代码。我们把栈地址设为 ‘sp_top’ (指令 LDR 会把 ‘sp_top’ 的值赋给栈指针 ‘sp’)。指令BL让处理器跳转到 ‘my_init’ (之前在 ‘init.c’ 中定义过)。然后处理器会使用 ‘B .’ 进入一个无限循环,就像 ‘while(1)’ 或者 ‘for(;;)’ 差不多。如果不这么做,系统会崩溃,嵌入式开发的基础思想就是让我们的代码运行在一个无限循环。

最后,为这两个文件写一个链接脚本 (‘linker.ld’):

1
2
3
4
5
6
7
8
9
10
ENTRY(_Start)
SECTIONS
{
. = 0x10000;
startup : { startup.o(.text)}
.data : {*(.data)}
.bss : {*(.bss)}
. = . + 0x500;
sp_top = .;
}

第一行告诉连接器,入口是 ‘_Start’ (在 ‘startup.s’ 中定义)。因为是一个基本的程序,我们不考虑中断。使用 ‘-kernel’ 选项的时候,QEMU 模拟器从地址 ‘0x10000’ 开始执行代码,所以我们在第四行把代码的起始地址设为 ‘0x10000’。’SECTIONS’ 部分用来定义一个程序中的不同部分。在这个部分中,’startup.o’ 构成了数据 (代码) 段,下面是数据段和 BSS 段,最后是定义栈段的地址。栈通常向下延伸,所以最好给一个安全地址。我们的代码很少,所以可以吧栈段放在当前地址距离 ‘0x500’ 的地方。变量 ‘sp_top’ 将储存这个地址。

写码阶段完成了,下面开始编译和链接。

汇编 ‘startup.s’ 文件:

1
$ arm-none-eabi-as  -mcpu=arm926ej-s startup.s -o startup.o

编译 ‘init.c’:

1
$ arm-none-eabi-gcc -c -mcpu=arm926ej-s init.c -o init.o

连接成 ELF 文件:

1
$ arm-none-eabi-ld -T linker.ld init.o startup.o -o output.elf

最后生成二进制文件:

1
$ arm-none-eabi-objcopy -O binary output.elf output.bin

上面的部分很容易理解,工具都在 ARM 工具链中,如有不明白的,去看看帮助文档。

苦尽甘来,现在在 QEMU 上运行看看:

1
$ qemu-system-arm -M versatilepb -nographic -kernel output.bin

上面的命令在前两篇文章解释过,所以不再细说了。二进制文件执行后向 ARM926EJ-S 的 UART0 串口写消息 “Hello Open World”,QEMU 将会把它输出到终端。

相关文章

QEMU快速使用指南 (译)

使用QEMU进行嵌入式系统开发 第1部分 (译)

使用QEMU进行嵌入式系统开发 第2部分 (译)

使用QEMU进行嵌入式系统开发 第3部分 (译)

如果您有疑问或建议,请在下方评论区域留言

遵循 BY-NC-ND 协议

评论功能加载中...