用Qemu模拟ARM(1)linux
前面已经安装并配置了编译链和qemu,如今能够用qemu来模拟arm平台了。ios
1. Hello, Qemu!编程
输入下面的代码:安全
hello.c - hello.c架构
#include<stdio.h> int main() { printf("Hello, Qemu!\n"); return 0; } |
编译并运行:app
$ arm-none-linux-gnueabi-gcc -o hello hello.c -static $ qemu-arm ./hello $ file hello hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), \ statically linked, for GNU/Linux 2.6.16, not stripped
不加-static变量的话,运行时则须要使用-L选项连接到相应的运行库函数
$ qemu-arm -L /home/dash/CodeSourcery/\ Sourcery_CodeBench_Lite_for_ARM_GNU_Linux/\ arm-none-linux-gnueabi/libc/ ./hello_1 Hello, Qemu! $ file hello_1 hello_1: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),\ dynamically linked (uses shared libs), for GNU/Linux 2.6.16, not stripped
动态编译和静态编译生成的文件大小差异:工具
$ ls -l -h total 656K -rwxr-xr-x 1 dash root 640K Jul 7 18:46 hello -rwxr-xr-x 1 dash root 6.6K Jul 7 18:48 hello_1
系统里安装了两套编译链arm-none-eabi-和arm-none-linux-eabi-,很容易让人混淆,可参考编译链的命名规则:post
arch(架构)-vendor(厂商名)–(os(操做系统名)–)abi(Application Binary Interface,应用程序二进制接口)测试
举例说明:
两种编译链的主要区别在于库的差异,前者没有后者的库多,后者主要用于在有操做系统的时候编译APP用的。前者不包括标准输入输出库在内的不少C标准库,适合于作面向硬件的相似单片机那样的开发。于是若是采用arm-none-eabi-gcc来编译hello.c会出现连接错误。
qemu-arm和qemu-system-arm的区别:
2. 使用qemu-system-arm运行Linux内核
从www.kernel.org下载最新内核,然后解压
$ tar xJf linux-3.10.tar.xz $ cd linux-3.10 $ make ARCH=arm versatile_defconfig $ make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
上面的命令指定内核架构为arm,交叉编译链为arm-none-linux-gnueabi, 须要在make menuconfig弹出的窗口中选择到 “Kernel Features”, 激活“Use the ARM EABI to compile the kernel”, 若是不激活这个选项的话,内核将没法加载接下来要制做的initramfs。
若是须要在u-boot上加载内核,就要编译为uImage的格式,uImage经过mkimage程序来压缩的,ArchLinux的yaourt仓库里能够找到这个包:
$ yaourt -S mkimage
安装好mkimage后,开始编译内核,由于CPU有4核,因此开启了-j8选项以加速编译:
$ make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- all -j8 uImage
接下来咱们能够在qemu-system-arm中测试咱们的内核了
$ qemu-system-arm -M versatilepb -m 128M -kernel ./arch/arm/boot/uImage
在弹出的窗口中能够内核运行到了kernel panic状态,这是由于内核没法加载root镜像的缘故,咱们将制做一个最简单的hello world的文件系统,告知kernel运行之。
init.c - init.c
#include <stdio.h> void main() { printf("Hello World!\n"); while(1); } |
编译并制做启动镜像:
$ arm-none-linux-gnueabi-gcc -o init init.c -static $ echo init |cpio -o --format=newc > initramfs 1280 blocks $ file initramfs initramfs: ASCII cpio archive (SVR4 with no CRC)
接下来咱们回到编译目录下执行:
$ qemu-system-arm -M versatilepb -kernel ./arch/arm/boot/uImage -initrd ../initramfs -serial stdio -append "console=tty1"
这时候能够看到,kernel运行并在Qemu自带的终端里打印出”Hello World!“。
若是咱们改变console变量为ttyAMA0, 将在启动qemu-system-arm的本终端上打印出qemu的输出。
用Qemu模拟ARM(2)
1. 关于Bootloader:
(引导程序)位于电脑或其余计算机应用上,是指引导操做系统启动的程序。引导程序启动方式和程序视应用机型种类而不一样。例如在普通的我的电脑上,引导程序一般分为两部分:第一阶段引导程序位于主引导记录(MBR),用以引导位于某个分区上的第二阶段引导程序,如NTLDR、GNU GRUB等。
嵌入式系统中常见的Bootloader主要有如下几种:
2. 关于“裸机编程(Bare-Metal)”:
微控制器开发人员很熟悉这个概念, Bare-Metal是指的你的程序和处理器之间没有任何东西——你写的程序将直接运行在处理器上, 换言之,开发人员是在直接操控硬件。在裸机编程的场景中,须要由开发人员检查并排除任何一个能够致使系统崩溃的风险。
“Bare-Metal”要求开发人员了解关于硬件的细节,因此接下来咱们将对编译链和qemu自己进行分析。
3. 下载qemu源码包并查询相关硬件信息:
ArchLinux采用ABS(Arch Build System)来管理源码包,下面的步骤将qemu源码包下载到本地,更详细的关于ABS的操做能够在ArchLinux的Wiki中找到
$ pacman -S abs $ pacman -Ss qemu extra/qemu 1.4.2-2 [installed] $ abs extra/qemu $ cp -r /var/abs/extra/qemu/ ~/abs $ cd ~/abs && makepkg -s --asroot -o
获得versatilepb开发板的CPU型号, 能够看到”arm926”是咱们要的结果。
$ grep "arm" src/qemu-1.4.2/hw/versatilepb.c #include "arm-misc.h" static struct arm_boot_info versatile_binfo; args->cpu_model = "arm926"; cpu = cpu_arm_init(args->cpu_model); cpu_pic = arm_pic_init_cpu(cpu); arm_load_kernel(cpu, &versatile_binfo);
获得versatilepb开发板的串口寄存器硬件信息:
$ grep "UART*" src/qemu-1.4.2/hw/versatilepb.c /* 0x10009000 UART3. */ /* 0x101f1000 UART0. */ /* 0x101f2000 UART1. */ /* 0x101f3000 UART2. */
因此说开源是王道嘛,很快就查到了每个须要了解的细节。UART0在内存中map到的地址是0x101f1000, 咱们直接往这个地址写数据,就能够在终端上看到数据输出了。
4. 查看编译链支持的平台:
$ cat ~/CodeSourcery/Sourcery_CodeBench_Lite_for_ARM_EABI/share/doc/arm-arm-none-eabi/info/gcc.info | grep arm926 `arm926ej-s', `arm940t', `arm9tdmi', `arm10tdmi', `arm1020t',
arm926ej-s是被支持的,所以咱们能够用这套编译链来生成须要的裸机调试代码。
5. 启动应用程序init.c的编写:
首先建立应用程序init.c:
init.c - init.c 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"); } |
init.c中,咱们首先声明一个volatile变UART0_PTR,volatile关键字用于告知编译器此变量是用于直接访问内存映像设备的,即串口0内存地址
display()函数则是用于将字符串中的字符按顺序输出到串口0, 直到遇到字符串结尾。
my_init()调用了display(), 接下来咱们将把它做为C入口函数.
预编译init.c:
$ arm-none-eabi-gcc -c -mcpu=arm926ej-s init.c -o init.o
6. 启动代码start.s编写:
start.s - start.s .global _Start _Start: LDR sp, = sp_top BL my_init B . |
处理器加电后,将跳转到指定的内存地址,今后地址开始读入并执行代码。
_Start被声明为全局函数,_Start的实现中,首先将栈地址指向sp_top, LDR(load), sp是栈地址寄存器(stack pointer),
BL则是跳转指令,跳转到my_init函数,事实上你能够跳转到任何一个你想跳转的函数,临时写一个their_init()跳转过去也行。Debug时常更改这里以调试不一样的子系统功能。
“B.”能够理解为汇编里的while(1)或for(;;)循环,处理器空转,什么也不作。若是不调用它,系统就会崩溃。所谓嵌入式编程的一个基本理念就是,代码无限循环。
预编译汇编文件start.s:
$ arm-none-eabi-as -mcpu=arm926ej-s startup.s -o startup.o
7. 接下来咱们须要用一个能够被编译器识别的连接脚本连接两文件, linker.ld:
ENTRY(_Start) SECTIONS { . = 0x10000; startup : { startup.o(.text)} .data : {*(.data)} .bss : {*(.bss)} . = . + 0x500; sp_top = .; } |
ENTRY(_Start)用于告知连接器程序的入口点(entry point)是_Start(start.s中定义). Qemu模拟器若是加上-kernel选项时,将自动从0x10000开始执行,因此咱们必须将代码放到这个地址。因此第四行咱们指定”. = 0x10000”. SECTIONS就是用于定义程序的不一样部分的。
startup.o组成了代码的text部分,而后是data部分和bss部分,最后一步则定义了栈指针(sp, stack pointer)地址. 栈一般是向下增加的,因此最好给它一个比较安全的地址, . = .+0x500就是用于避免栈被改写的。sp_top用于存储栈顶地址。
有关程序结构:
编译:
$ arm-none-eabi-ld -T linker.ld init.o startup.o -o output.elf $ file output.elf output.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),statically linked, not stripped $ arm-none-eabi-objcopy -O binary output.elf output.bin $ file output.bin output.bin: data
8. 使用qemu-system-arm运行output.bin:
$ qemu-system-arm --help | grep nographic -nographic disable graphical output and redirect serial I/Os to console. $ qemu-system-arm -M versatilepb -nographic -kernel output.bin Hello Open World
9. Play more tricks: 改动init.c里的串口输出地址为串口1:
volatile unsigned char * const UART0_PTR = (unsigned char *)0x0101f2000; // 0x101f1000 --> 0x101f2000
按照步骤3~7里从新编译,并运行以查看结果:
# 没有反应! $ qemu-system-arm -M versatilepb -nographic -kernel output.bin # 终端有输出字符。 $ qemu-system-arm -M versatilepb -kernel output.bin -serial vc:800x600 -serial stdio Hello Open World
一样你也能够把字符输出到第三个串口,只不过前两个-serial的重定向须要指定到别的设备而已。
1. 下载并交叉编译u-boot。
新版本的u-boot我加载后总有问题,2009.11版则能够顺利经过编译和测试。
$ wget ftp://ftp.denx.de/pub/u-boot/u-boot-2009.11.tar.bz2 $ tar xjvf u-boot-2009.11.tar.bz2 $ cd u-boot-2009.11 $ make versatilepb_config arch=ARM CROSS_COMPILE=arm-none-eabi- $ make all arch=ARM CROSS_COMPILE=arm-none-eabi-
编译完成后会在目录下生成u-boot.bin和u-boot文件。
2. 运行u-boot.bin:
$ qemu-system-arm -M versatilepb -kernel u-boot.bin -nographic
若是采用-nographic来运行qemu-system-arm,终端将没法再响应任何系统输入譬如Ctrl+c/ctrl+d_,要终止qemu-system-arm就只能查到进程号再kill。因此我通常不带-nographic选项,启动后ctrl+alt+2去看serial0输出,保留在终端窗口直接ctrl+c杀死qemu-sytem-arm进程的权力。
3. 用u-boot引导镜像文件:
改动上一篇文章里用于构建启动镜像的linker.ld文件,由于u-boot.bin文件大小的缘故,咱们须要把启动镜像的起始地址总体上移.
$ ls -l -h u-boot.bin -rwxr-xr-x 1 dash root 85K Jul 8 15:57 u-boot.bin
linker.ld文件里, 0x100000,这个大小相比于85K显然已经足够。
ENTRY(_Start) SECTIONS { . = 0x100000; startup : { startup.o(.text)} .data : {*(.data)} .bss : {*(.bss)} . = . + 0x500; sp_top = .; }
按上一章的编译方法生成output.bin,再也不重述。
使用mkimage工具建立u-boot可识别的image文件:
$ mkimage -A arm -C none -O linux -T kernel -d output.bin -a 0x00100000 -e 0x00100000 output.uimg Image Name: Created: Mon Jul 8 16:04:11 2013 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 152 Bytes = 0.15 kB = 0.00 MB Load Address: 00100000 Entry Point: 00100000 $ file *.uimg output.uimg: u-boot legacy uImage, , Linux/ARM, OS Kernel Image (Not \ compressed), 152 bytes, Mon Jul 8 16:04:11 2013, Load Address: 0x00100000,\ Entry Point: 0x00100000, Header CRC: 0x3C62F575, Data CRC: 0x69CE9647
将u-boot.bin和output.uimg打包为一个文件:
$ cat u-boot.bin output.uimg >flash.bin
下面这条命令用于计算output.img在使用u-boot加载完flash.bin后在内存中的地址,-kernel选项告诉qemu从0x100000开始加载镜像,即65536。 65536+u-boot.bin后的大小,即output.img在内存中的地址。printf则是用16进制的格式打印出来,以便加载.
$ printf "0x%X" $(expr $(stat -c%s u-boot.bin) + 65536) 0x2525C
启动qemu-system-arm并运行自定义镜像:
$ qemu-system-arm -M versatilepb -nographic -kernel flash.bin # iminfo 0x2525c ## Checking Image at 0002525c ... Legacy image found Image Name: Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 152 Bytes = 0.1 kB Load Address: 00100000 Entry Point: 00100000 Verifying Checksum ... OK VersatilePB # bootm 0x2525c ## Booting kernel from Legacy Image at 0002525c ... Image Name: Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 152 Bytes = 0.1 kB Load Address: 00100000 Entry Point: 00100000 Loading Kernel Image ... OK OK Starting kernel ... Hello Open World
u-boot能够支持的选项还有不少,包括使用NFS/TFTP启动等等,留待之后慢慢研究。