没开发板做Linux嵌入式开发?虚拟机搞定一切!

使用Qemu搭建zImage以及uImage启动的vexpress-a9 Linux虚拟机

Posted by Anriku on December 24, 2018

在做嵌入式开发的过程中很多也许都会用到开发版。那么可以不那么麻烦吗?我只是想稍微学一下嵌入式相关的知识就需要买一个开发板。这篇blog将会教你如何通过Qemu来搭建虚拟的开发环境。

这里我使用的是VirtulBox虚拟机;Linux版本为Ubuntu 18.04.1 LTS。

文件结构

下面是构建完成的文件结构图:

arm_linux_architecture

  • 首先用户目录下建立了一个vexpress-a9目录作为工作目录
  • busybox-1.29.3是编译linux所需的指令的目录
  • Linux4.10.8是linux内核。
  • SimpleLinuxDriver是一个简单的Linux字符驱动文件
  • U-boot-2018.11是u-boot目录
  • create_fs.sh是一个构建文件系统的shell脚本
  • roots.ext3是一个构建完成的文件系统
  • start_qemu.sh是启动qemu的shell脚本
  • hello.c是一个C程序,a.out通过交叉编译器编译的可执行文件。

配置交叉编译环境

由于嵌入式的设备和通用桌面的设备的硬剑环境是不一样的。从根本上来说是它们的指令系统(比如说电脑一般是x86指令系统,而嵌入式设备很多是arm的指令系统)是不一样的,因此它们编译的软件也是不一样的。这里我们要装的vexpress-a9是arm体系的。需要安装下面的交叉编译环境:

sudo apt-get install gcc-arm-linux-gnueabi  //gcc
sudo apt-get install g++-arm-linux-gnueabi  //g++

编译Linux内核

官网地址这里使用的是linux-4.10.8.tar.gz版本。

内核的地址在https://www.kernel.org/pub/ -> linux -> kernel -> v4.x中。

下面我们将进行两个不同类型的Linux内核镜像的编译。一个是zImage、一个uImage。其实两个都是编译出来的linux二进制文件进行压缩得到的。主要的不同就是zImage可以直接在qemu上进行运行,而uImage是通过u-boot来进行引导的。

编译zImage

export ARCH=arm // 说明是arm体系
export CROSS_COMPILE=arm-linux-gnueabi- // 使用的交叉编译器
make vexpress_defconfig // 使用内核中的默认vexpress架构的配置
make zImage // 编译zImage
make dts // 编译设备树
make modules // 编译内核外模块

上面需要说明一点的就是make vexpress_defconfig。这是设置linux内核中默认vexpress的设置到.config文件中。这里有在linux解压目录下使用find . -name *defconfig* 可以看到所有的默认配置。有arm各架构的、powerpc各架构的等等。

另一点需要特别注意的是,现在高版本的内核中没有包含所有硬件的描述了,而是通过设备树来进行描述的因此高版本的内核需要加载两个镜像一个是内核镜像,另一个就是设备树。因此需要通过make dtbs来进行设备树的编译。

编译完成后你可以在/…/linux-4.10.8/arch/arm/boot目录下发现生成了zImage以及其它相关的文件。

编译uImage

export ARCH=arm // 说明是arm体系
export CROSS_COMPILE=arm-linux-gnueabi- // 使用的交叉编译器
export LOADADDR=0x60003000 // 设置uImage启动的地址
make vexpress_defconfig // 使用内核中的默认vexpress架构的配置
make modules // 编译内核外模块
make dtbs // 编译设备树
make uImage // 编译uImage

编译完成后你可以在/.../linux-4.10.8/arch/arm/boot目录下发现生成了uImage以及其它相关的文件。

编译u-boot

官网下载,这里我们使用u-boot-2018-11.tar.bz2,当然你可以下更新的。

export ARCH=arm // 说明是arm体系
export CROSS_COMPILE=arm-linux-gnueabi- // 使用的交叉编译器
make vexpress_ca9x4_defconfig // 使用u-boot中默认的vexpress_ca9x4的配置
make //编译

这里的默认配置,你仍然可以在u-boot解压的目录下执行find . -name *defconfig*进行查看。

编译成功后你就可以在/…/u-boot-2018-11目录下发现生成了uboot以及其它相关的文件。

运行下面的命令得到下图的效果就说明成功了:

qemu-system-arm -M vexpress-a9 -m 256M -nographic -kernel u-boot

u-boot-finish-compile

编译busybox工具集并构建文件系统

官网下载,这里我们使用最新的稳定版busybox-1.29.3.tar.bz2

busybox是Linux平台上的一个工具集。busybox最大的特点是把不同的工具代码以及公用代码都集成在一起,从而大大减少了可执行文件的体积。非常适合资源有限的嵌入式设备。

编译busybox

首先,通过make menuconfig进行一下简单的设置。这里就简单设置两个地方。

下面是两个地方设置的目录深度:

  • settings
    • Build static binary(no shared libs)按y选择 //使用静态编译
    • (arm-linux-gnueabi-)Cross compiler prefix。 //设置交叉编译器前缀

下面再给一张设置后的图:

busy_box

然后进行编译:

make // 编译
make install //默认安装都<busybox dir>/_install中

编译完成后,就会在busybox的目录下生成_install文件夹。这个文件夹将会在下面构建文件系统的时候添加到文件系统中去,这个文件夹就包含了很多Linux必要的命令。其中你会发现这些都是bin目录下busybox的软链接,这样就使通过共享大大减少了工具集的大小

文件系统的构建

将下面的代码写在一个shell脚本中进行运行:

#!/bin/bash
# 1.先进行清理操作
sudo rm -rf rootfs
sudo rm -rf tmpfs
sudo rm -f rootfs.ext3

sudo mkdir rootfs
# 2.创建Linux中的必要文件夹
sudo mkdir -p roots/bin # /bin包含普通用户和超级用户都能使用的命令
sudo mkdir -p rootfs/sbin # /sbin包含系统运行的关键可执行文件以及一些管理程序
sudo mkdir -p rootfs/home # /home普通用户的工作目录,没有普通用户都会在这里建立一个文件夹
sudo mkdir -p rootfs/etc # /etc存放系统配置文件以及应用程序的配置文件
sudo mkdir -p rootfs/lib # /lib存放所有应用程序的共享文件以及内核模块
sudo mkdir -p rootfs/proc/ # /proc目录是内核在内存中映射的实时文件系统,存放内核向用户应用程序提供的信息文件
sudo mkdir -p rootfs/sys/ # /sys是文件系统挂载的地方
sudo mkdir -p rootfs/tmp/ # /tmp存放系统或应用程序产生的临时文件
sudo mkdir -p rootfs/root/ # /root是超级用户的用户目录
sudo mkdir -p rootfs/var/ # /var存放假脱机数据以及系统日志
sudo mkdir -p rootfs/mnt/ # /mnt用于加载磁盘分区和硬件设备挂载点
sudo mkdir -p roots/usr # /usr包含所有用户的二进制文件和库文件等
sudo mkdir -p rootfs/dev/ # /dev用于存放设备文件

# 3.将busybox中的编译的可执行文件放到rootfs下
sudo cp -arf ${BUSY_BOX}/_install/*  rootfs/
sudo cp -arf ${ETC} rootfs/

# 4.添加交叉编译环境
sudo cp -arf /usr/arm-linux-gnueabi/lib rootfs/
sudo rm rootfs/lib/*.a
sudo arm-linux-gnueabi-strip rootfs/lib/*

# 5.创建设备文件
sudo mknod rootfs/dev/tty1 c 4 1
sudo mknod rootfs/dev/tty2 c 4 2
sudo mknod rootfs/dev/tty3 c 4 3
sudo mknod rootfs/dev/tty4 c 4 4
sudo mknod rootfs/dev/console c 5 1
sudo mknod rootfs/dev/null c 1 3

# 6.生成一个空的文件作为文件系统
sudo dd if=/dev/zero of=rootfs.ext3 bs=1M count=32
sudo mkfs -t ext3 rootfs.ext3
# 7.将文件系统挂载到tmpfs目录下
sudo mkdir -p tmpfs
sudo mount -t ext3 rootfs.ext3 tmpfs/ -o loop
# 8.将之前创建的文件系统相关的文件放到通过tmpfs放到rootfs.ext3文件系统中去
sudo cp -r rootfs/*  tmpfs/
sudo umount tmpfs

现在对上面的文件系统构建步骤进行一下梳理:

  • 先做一下清理操作
  • 创建arm-linux的必要文件夹
  • 将busybox生成的命令以及etc配置文件放到arm-linux文件系统中去。这里有两个环境变量是需要在执行之前设置的。
  • 将交叉编译环境放到arm-linux文件系统中去,这样最后运行Hello程序才能运行
  • 创建必要的设备文件
  • 通过dd、mkfs -t etx4命令制作文件系统。其中dd命令是一个很有用的命令。它可以用来进行设置分区、设置内存交换空间等操作
  • 然后将dd出来的roofs.ext3挂载到tmpfs文件夹下,并且把刚才rootfs中配置好的文件放到tmpfs中去。也就是放在了roofs.ext3中。
  • 最后取消roofs.ext3的挂载

最后得到的roofs.ext3就是我们的一个简单的arm-linux的文件系统。

下面在vexpress-a9目录下执行构建文件系统的操作:

export BUSY_BOX=./busybox-1.29.3 // 设置busybox的环境变量进行命令的转移
export ETC=./busybox-1.29.3/examples/bootfloppy/etc // 使用busybox中默认的配置
执行上面的shell脚本,自己创建一个脚本文件

OK,整个过程没有问题,应该可以看到vexpress-a9目录下生产对应的文件系统rootfs.ext3

使用qemu运行arm-linux

在编译Linux内核的时候,编译出来了zImageuImage两种压缩linux镜像。因此使用qemu运行linux也有两种方式。分别是运行zImage和运行uImage。uImage是通过u-boot来进行加载运行的,所以要稍微复杂一点。

安装qemu

sudo apt-get install qemu qemu-system qemu-utils

运行zImage

zImage是可以直接运行在qemu上的linux镜像。使用下面的命令就可以运行zImage了。

sudo qemu-system-arm -M vexpress-a9 -m 512M -dtb ~/vexpress-a9/linux-4.10.8/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -kernel ~/vexpress-a9/linux-4.10.8/arch/arm/boot/zImage -append "root=/dev/mmcblk0 console=tty0 init=/linuxrc" -sd ~/vexpress-a9/rootfs.ext3
  • qemu-system-arm说明模拟的是arm体系的虚拟机
  • -M指定的是虚拟机的类型
  • -m 指定的是虚拟的RAM大小
  • -dtb是指定设备树,也就是前面在编译内核的时候上面……/dts/文件夹下生成的设备树。这个在高版本的Linux中必须要添加这个选项才能运行。低版本的Linux把设备信息放在内核中就不需要这个选项
  • -kernel 指定运行的内核。这个选项要么直接是Linux内核或者是其它的多引导格式(eg. u-boot)
  • -append 指定的是添加虚拟机启动参数。root用于指定根设备(mmcblk0是后面我们设置的sd卡),console指定控制台,init指定在Linux启动的时候执行之前busybox编译的linuxrc文件(其实它也是一个busybox软链接)。
  • -sd 是用来添加一张sd卡的这里添加的就是前面构建的文件系统。

运行成功的效果图如下:

zimage-success

运行uImage

运行uImage这里需要同u-boot来进行内核的加载。这个加载的过程中需要使用到tftp来进行内核镜像的传输。tftp是一种基于udp的简单tfp传输软件。默认端口是69

安装tftp

sudo apt-get install xinetd tftpd tftp tftp-hpa tftpd-hpa

其中xinetd是用于处理网络请求的。当主机收到请求xinetd守护进程就会根据其端口好将其交给对应的程序。tftpd、tftp、tftp-hpa tftpd-hpa是和tftp相关的。

配置tftp

打开/etc/xinetd.d/tftp配置文件,修改为如下:

service tftp
{
     protocol = udp
     port = 69
     socket_type = dgram
     wait = yes
     user = anriku // 这个是你linux下boot的用户名,当然后面会把boot目录下的访问权限都设置为777这个配置就没有太大的用处了
     server = /usr/sbin/in.tftpd
     server_args = ~/vexpress-a9/linux-4.10.8/arch/arm/boot // 这里配置的是ftp传送目录
     disable = no
}

这里主要就是配置server_args也就是ftp进行传送的目录。

然后打开/etc/default/tftpd-hpa进行ftp服务器配置:

# /etc/default/tftpd-hpa 
TFTP_USERNAME="tftp"
TFTP_DIRECTORY= "~/vexpress-a9/linux-4.10.8/arch/arm/boot" // 这里配置的是ftp传送目录                      
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="-l -c -s"

完成配置后执行下面的命令:

// 下面把tftp需要传送文件的目录的权限给到最大
sudo chmod -R 777 ~/vexpress-a9/linux-4.10.8/arch/arm/boot
// 重启tftpd-hpa
sudo service tftpd-hpa restart
// 重启xinetd服务
sudo /etc/init.d/xinetd reload
sudo /etc/init.d/xinetd restart

OK,下面你可以通过下面命令来验证设置好没有:

tftp <ubuntu的ip地址>
get uImage

如果get uImage获取了uImage就说明设置成功了。

网桥配置

这里如果你使用的是VirtulBox运行的Ubuntu的话,你需要将网络连接方式设置为host-only模式。不然ftp无法进行传输。

首先下载下面的软件:

apt-get install bridge-utils        # 虚拟网桥工具。brctl命令会用到
apt-get install uml-utilities       # UML(User-mode linux)工具。tunctl命令会用到

然后,下面建立网桥:

sudo brctl addbr br0 // 建立网桥br0
sudo tunctl -t tap0 -u anriku // 建立一个tap设备,-t表示创建tap设备,-u表示用户这里填你的用户名
sudo ifconfig enp0s3 down // ubuntu宿主机的网络接口关闭

sudo brctl addif br0 tap0 // 给网桥添加tap0作为一个接口
sudo brctl addif br0 enp0s3 // 给网桥添加enp0s3作为一个接口

sudo ifconfig br0 192.168.1.1 netmask 255.255.255.0 // 为br0设置ip地址
sudo ifconfig tap0 192.168.1.2 netmask 255.255.255.0 // 为tap0设置ip地址
sudo ifconfig enp0s3 192.168.1.3 netmask 255.255.255.0 // 为enp0s3设置ip地址

OK这样网桥就设置完成了。ping一下通了的话就没问题了。

然后通过qemu启动u-boot:

sudo qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/anriku/vexpress-a9/u-boot-2016.05/u-boot -nographic -net nic -net tap,ifname=tap0 -sd ~/vexpress-a9/rootfs.ext3

启动u-boot和启动内核镜像是差不多的。需要说明的就两点。

  • 一个是-net nic是用来给虚拟机创建一张网卡的,-net tap是对tap设备进行设置这里就设置为刚才在ubuntu宿主机上创建的tap0
  • 另一个-nographic是不使用图像界面

执行上面的命令后,就会加载u-boot了,在u-boot下执行下面的命令:

// 设置arm虚拟机的ip地址
setenv ipaddr 192.168.1.11
// 设置服务器的ip地址
setenv serverip 192.168.2 

// ping一下最后如果说alive就说明设置成功了

// 成功之后就获取uIamge,其中0x60003000设置在编译uImage的时候设置的地址
tftp 0x60003000 uImage 
// 加载设备树到0x70003000地址上
tftp 0x70003000 dts/vexpress-v2p-ca9.dtb

// 设置启动参数和之前zImage设置qemu的参数是一样的
setenv bootargs 'root=/dev/mmcblk0 console=ttyAMA0 init=/linuxrc' 
// 启动uImage(uImage地址 + ramdisk地址 + 设备树镜像地址)
bootm 0x60003000 - 0x70003000

uImage运行成功的效果图如下:

uimage-success

使用nfs使宿主机与arm-linux主机进行通信

宿主机配置

首先,在宿主机上安装nfs软件:

apt-get install nfs-kernel-server

然后,在/etc/exports文件中插入下面一行代码:

*(rw,no_root_squash,insecure)

最后,启用nfs软件并且启动qemu:

// 重启服务
service nfs-kernel-server start
// 启动qemu
sudo qemu-system-arm -M vexpress-a9 -m 512M -dtb ~/vexpress-a9/linux-4.10.8/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -kernel ~/vexpress-a9/linux-4.10.8/arch/arm/boot/zImage -append "root=/dev/mmcblk0 console=tty0 init=/linuxrc" -sd ~/vexpress-a9/rootfs.ext3

arm-linux虚拟机配置

// 通过zImage启动Linux
sudo qemu-system-arm -M vexpress-a9 -m 512M -dtb ~/vexpress-a9/linux-4.10.8/arch/arm/boot/dts/vexpress-v2p-ca9.dtb -kernel ~/vexpress-a9/linux-4.10.8/arch/arm/boot/zImage -append "root=/dev/mmcblk0 console=tty0 init=/linuxrc" -sd ~/vexpress-a9/rootfs.ext3

// 启用网卡
ifconfig eth0 10.0.2.15 up 
// 设置默认网关
route add defa.ult gw 10.0.2.2 
// 挂载nfs文件系统
mount -t nfs -o nolock 192.168.1.241:/ /mnt

OK,现在就可以在mnt目录下对宿主机的文件系统进行访问了。

下面是在arm-linux上运行宿主机上的hello.c程序的效果图:

nfs

总结

本篇博客有下面主要介绍了下面几点:

  • Linux内核的编译。主要可以编译成两种格式。一种是的qemu可以直接运行的zImage,另一种是通过u-boot来进行启动的uImage格式。需要特别注意的就是Linux3.5版本后需要编译设备树并做相关的配置才能启动虚拟机。
  • u-boot的编译
  • 利用busybox来编译嵌入式Linux的指令工具集,并制作文件系统
  • 通过qemu分别启动uImage和zImage的arm-linux虚拟机。uImage启动方式稍微复杂点需要对宿主机做网桥配置
  • 通过nfs来来使宿主机和arm-linux虚拟机通信。

参考

Linux document

u-boot document

qemu document

qemu模拟vexpress-a9及u-boot引导 linux

《ARM嵌入式Linux系统开发详解》(弓雷)

转载请注明链接地址