一、环境
实验环境如下:
主机:x86_64
操作系统:Ubuntu 20.04.6 LTS
Qemu版本:QEMU emulator version 4.2.1
Linux内核版本:linux-4.4.240
Busybox版本:busybox-1.35.0
二、前置准备
下载 linux-4.4.240
源码文件: linux-4.4.240
下载 busybox-1.35.0
:busybox-1.35.0
下载 qemu
:
sudo apt install qemu-system-arm
qemu
版本号如下:
QEMU emulator version 4.2.1 (Debian 1:4.2-3ubuntu6.30)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
可以使用以下命令 qemu-system-arm -M help
查看 qemu
支持哪些 ARM 开发板,本文将选择 vexpress-a9
和 vexpress-a15
开发板进行模拟:
$ qemu-system-arm -M help
Supported machines are:
akita Sharp SL-C1000 (Akita) PDA (PXA270)
ast2500-evb Aspeed AST2500 EVB (ARM1176)
ast2600-evb Aspeed AST2600 EVB (Cortex A7)
borzoi Sharp SL-C3100 (Borzoi) PDA (PXA270)
canon-a1100 Canon PowerShot A1100 IS
cheetah Palm Tungsten|E aka. Cheetah PDA (OMAP310)
collie Sharp SL-5500 (Collie) PDA (SA-1110)
connex Gumstix Connex (PXA255)
cubieboard cubietech cubieboard (Cortex-A8)
emcraft-sf2 SmartFusion2 SOM kit from Emcraft (M2S010)
highbank Calxeda Highbank (ECX-1000)
imx25-pdk ARM i.MX25 PDK board (ARM926)
integratorcp ARM Integrator/CP (ARM926EJ-S)
kzm ARM KZM Emulation Baseboard (ARM1136)
lm3s6965evb Stellaris LM3S6965EVB
lm3s811evb Stellaris LM3S811EVB
mainstone Mainstone II (PXA27x)
mcimx6ul-evk Freescale i.MX6UL Evaluation Kit (Cortex A7)
mcimx7d-sabre Freescale i.MX7 DUAL SABRE (Cortex A7)
microbit BBC micro:bit
midway Calxeda Midway (ECX-2000)
mps2-an385 ARM MPS2 with AN385 FPGA image for Cortex-M3
mps2-an505 ARM MPS2 with AN505 FPGA image for Cortex-M33
mps2-an511 ARM MPS2 with AN511 DesignStart FPGA image for Cortex-M3
mps2-an521 ARM MPS2 with AN521 FPGA image for dual Cortex-M33
musca-a ARM Musca-A board (dual Cortex-M33)
musca-b1 ARM Musca-B1 board (dual Cortex-M33)
musicpal Marvell 88w8618 / MusicPal (ARM926EJ-S)
n800 Nokia N800 tablet aka. RX-34 (OMAP2420)
n810 Nokia N810 tablet aka. RX-44 (OMAP2420)
netduino2 Netduino 2 Machine
none empty machine
nuri Samsung NURI board (Exynos4210)
palmetto-bmc OpenPOWER Palmetto BMC (ARM926EJ-S)
raspi2 Raspberry Pi 2
realview-eb ARM RealView Emulation Baseboard (ARM926EJ-S)
realview-eb-mpcore ARM RealView Emulation Baseboard (ARM11MPCore)
realview-pb-a8 ARM RealView Platform Baseboard for Cortex-A8
realview-pbx-a9 ARM RealView Platform Baseboard Explore for Cortex-A9
romulus-bmc OpenPOWER Romulus BMC (ARM1176)
sabrelite Freescale i.MX6 Quad SABRE Lite Board (Cortex A9)
smdkc210 Samsung SMDKC210 board (Exynos4210)
spitz Sharp SL-C3000 (Spitz) PDA (PXA270)
swift-bmc OpenPOWER Swift BMC (ARM1176)
sx1 Siemens SX1 (OMAP310) V2
sx1-v1 Siemens SX1 (OMAP310) V1
terrier Sharp SL-C3200 (Terrier) PDA (PXA270)
tosa Sharp SL-6000 (Tosa) PDA (PXA255)
verdex Gumstix Verdex (PXA270)
versatileab ARM Versatile/AB (ARM926EJ-S)
versatilepb ARM Versatile/PB (ARM926EJ-S)
vexpress-a15 ARM Versatile Express for Cortex-A15
vexpress-a9 ARM Versatile Express for Cortex-A9
virt-2.10 QEMU 2.10 ARM Virtual Machine
virt-2.11 QEMU 2.11 ARM Virtual Machine
virt-2.12 QEMU 2.12 ARM Virtual Machine
virt-2.6 QEMU 2.6 ARM Virtual Machine
virt-2.7 QEMU 2.7 ARM Virtual Machine
virt-2.8 QEMU 2.8 ARM Virtual Machine
virt-2.9 QEMU 2.9 ARM Virtual Machine
virt-3.0 QEMU 3.0 ARM Virtual Machine
virt-3.1 QEMU 3.1 ARM Virtual Machine
virt-4.0 QEMU 4.0 ARM Virtual Machine
virt-4.1 QEMU 4.1 ARM Virtual Machine
virt QEMU 4.2 ARM Virtual Machine (alias of virt-4.2)
virt-4.2 QEMU 4.2 ARM Virtual Machine
witherspoon-bmc OpenPOWER Witherspoon BMC (ARM1176)
xilinx-zynq-a9 Xilinx Zynq Platform Baseboard for Cortex-A9
z2 Zipit Z2 (PXA27x)
一个开发板可能支持不同的CPU,可以通过以下命令查看对应的开发板 vexpress-a9
支持哪些CPU:
$ qemu-system-arm -M vexpress-a9 --cpu help
Available CPUs:
arm1026
arm1136
arm1136-r2
arm1176
arm11mpcore
arm926
arm946
cortex-a15
cortex-a7
cortex-a8
cortex-a9
cortex-m0
cortex-m3
cortex-m33
cortex-m4
cortex-r5
cortex-r5f
max
pxa250
pxa255
pxa260
pxa261
pxa262
pxa270-a0
pxa270-a1
pxa270
pxa270-b0
pxa270-b1
pxa270-c0
pxa270-c5
sa1100
sa1110
ti925t
安装交叉编译工具链
sudo apt-get install crossbuild-essential-armhf
工具链版本如下:
$ arm-linux-gnueabi-gcc --version
arm-linux-gnueabi-gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
交叉编译 ARM32 Linux内核
cd linux-4.4.420/
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- O=build vexpress_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- O=build -j8
说明:
ARCH
:指定目标CPU架构;CROSS_COMPILE
:指定交叉编译器;O=build
:O是Out的缩写,表示编译输出文件放在build目录,不跟源码混在一起,保持源码的整洁性;- 要模拟
vexpress
开发板,所以选择vexpress_defconfig
配置文件(对应原厂默认内核配置文件arch/arm/configs/vexpress_defconfig
),make 的过程会根据原厂默认配置文件生成可直接用于编译的内核配置文件.config
。
查看下内核编译出来的原始内核文件 vmlinux
,是ARM 32-bit版本:
$ file build/vmlinux
build/vmlinux: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=1d1a062137cad67734d7da3c441fbda869a74e59, with debug_info, not stripped
vmlinux
不能直接引导Linux系统启动,能引导Linux系统启动的是 bzImage
文件,由 vmlinux
经objcopy
处理后的二进制内核映像,下文用到的内核镜像就是 bzImage
:
$ file build/arch/arm/boot/zImage
build/arch/arm/boot/zImage: ARM OpenFirmware FORTH Dictionary, Text length: -509607936 bytes, Data length: -509607936 bytes, Text Relocation Table length: -369098749 bytes, Data Relocation Table length: 24061976 bytes, Entry Point: 0x00000000, BSS length: 3505288 bytes
同时也会生成多个 vexpress
开发板的设备树文件,分别对应前文 ARM Versatile Express提到的三种开发板:
$ ls -l build/arch/arm/boot/dts/vexpress*.dtb
-rw-rw-r-- 1 wurusai wurusai 18770 3月 30 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dtb
-rw-rw-r-- 1 wurusai wurusai 13092 3月 30 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb
-rw-rw-r-- 1 wurusai wurusai 12714 3月 30 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca5s.dtb
-rw-rw-r-- 1 wurusai wurusai 14360 3月 30 13:56 build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb
交叉编译 ARM32 Busybox
BusyBox
将许多常见UNIX实用程序的微型版本组合到单个小型可执行文件中。用来代替通常在 GNU fileutils,shellutils
等大多数实用程序。BusyBox
中的实用程序通常比其功能齐全的GNU表亲少一些选项。但是,所包含的选项提供了预期的功能,并且其行为与GNU对应项非常相似。BusyBox
为任何小型或嵌入式系统提供了一个相当完整的环境。
$ cd busybox-1.35.0/
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
在 menuconfig
菜单中:
- 选中
Settings –> Build Options –> [*] Build static binary(no share libs)
; - 取消勾选
Shells ---> [ ] Job control
;
接着继续进行编译安装:
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j 8
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
安装完后会默认安装到源码目录的 _install/
目录下:
$ ls _install/
bin linuxrc sbin usr
最关键的就是 _install/bin/busybox
,其他都是链接文件:
$ file _install/bin/busybox
_install/bin/busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, BuildID[sha1]=742ae0c792359d80187b66b73e21511b9df9a880, for GNU/Linux 3.2.0, stripped
三、准备启动
方式一
使用busybox制作initramfs
创建虚拟 rootfs
中的 init
启动脚本,并赋予可执行权限:
$ cd busybox-1.35.0/_install/
$ mkdir proc sys dev tmp
$ touch init
$ chmod +x init
init
脚本内容如下:
#!/bin/sh
# 挂载一些必要的文件系统
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs none /tmp
mount -t devtmpfs none /dev
echo
echo "Hello 32-bit ARM Linux"
# 显示开机消耗时间
echo "This boot took $(cut -d' ' -f1 /proc/uptime) seconds"
echo
# 停留在控制台
exec /bin/sh
制作 initramfs
文件,它是多个文件通过 cpio
打包和 gzip
压缩的文件,是一个 cpio
格式的内存文件系统:
$ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
打包出来的文件如下:
使用QEMU启动ARM32 Linux内核
1.模拟 vexpress-a9
开发板
vexpress-a9
开发板,其处理器是 32-bit 的 Cortex-A9
,4核。
以字符界面方式启动QEMU(不启动图形界面),同时日志输出到控制台:
$ qemu-system-arm \
-M vexpress-a9 \
-cpu cortex-a9 \
-smp 4 \
-m 1G \
-nographic \
-kernel ./linux-4.4.420/build/arch/arm/boot/zImage \
-dtb ./linux-4.4.420/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-initrd ./busybox-1.35.0/initramfs.cpio.gz \
-append "init=/init console=ttyAMA0"
QEMU参数说明(更多可参考:Standard options):
-M
:指定模拟的开发板,可通过qemu-system-arm -M help
查看;-cpu
:指定模拟的cpu,可通过qemu-system-arm -M vexpress-a9 --cpu help
查看;-smp
:指定cpu核数量,启动后可以使用nproc
命令核对,vexpress-a9
最多只支持4核,可少不可多,超过启动会报错;-m
:指定内存大小,vexpress-a9
最大支持1GB
,可少不可多 ,超过启动会报错,启动后可以使用free -h
命令核对;-kernel
:指定启动的内核镜像;-initrd
:指定启动的内存文件系统;-append
:传递给内核的启动参数;启动后可使用cat /proc/cmdline
命令核对;-nographic
:启动字符界面(不启动图形界面),输出重定向到宿主机命令行,与参数console=ttyAMA0
组合使用。
2.模拟 vexpress-a15
开发板
以字符界面方式启动QEMU(不启动图形界面),同时日志输出到控制台:
$ qemu-system-arm \
-M vexpress-a15 \
-cpu cortex-a15 \
-smp 2 \
-m 2G \
-nographic \
-kernel ./linux-4.4.420/build/arch/arm/boot/zImage \
-dtb ./linux-4.4.420/build/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dtb \
-initrd ./busybox-1.35.0/initramfs.cpio.gz \
-append "init=/init console=ttyAMA0"
说明:
vexpress-a15
的CPU数最多支持2核,-smp
配置超过启动不会报错,但启动后只能看到2核;vexpress-a15
的内存最大支持2G,-m
配置超过启动不会报错,但启动后只能看到2G
内存。
注意:在客户机终端按下 ctrl + a
,再按一下 x
键就能退出 qemu
然后回到宿主机。
问题
在使用 QEMU 启动 Linux 内核时,如果通过 -initrd
参数直接加载一个预先打包好的 initramfs.cpio.gz
作为根文件系统(如你的命令所示),那么默认情况下 所有文件操作都仅在内存中进行,关闭 QEMU 后修改会丢失 。
当前方案的限制
- initramfs
是一个临时内存文件系统,所有文件在启动时解压到内存中,运行时修改仅影响内存副本,不会回写到原始的 .cpio.gz
文件。
- 如果在 QEMU 中通过 touch /newfile
或 rm /file
修改文件系统,这些改动仅在本次运行中有效。关闭 QEMU 后,下次启动仍会从原始的 .cpio.gz
文件加载初始状态。若想保留修改,必须重新打包 initramfs
并重启。
实验
1.先启动 qemu
2.尝试创建 /home
目录,创建一个文件 a.txt
3.退出后重新启动 qemu
由此可见之前的修改并没有生效!
方式二(推荐)
busybox 初始化配置
BusyBox
的编写考虑了尺寸的优化和有限的资源。它也是非常模块化的,因此可以在编译时轻松地包含或排除命令(或功能)。这使自定义嵌入式系统变得容易。要创建一个工作系统,只需在 /dev
中添加一些设备节点,在 /etc
中添加一些配置文件,以及一个 Linux
内核。
解压后编译 busybox
:
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- install
编译完成后在 busybox
目录下生成了 _intall
目录,将作为我们构建根文件系统的目录,在根文件系统目录下补充一些内容。 增加以下目录:
执行命令:
mkdir {etc,proc,sys,tmp,dev,lib}
etc
:主要存放一些配置文件如:inittab
(init
进程会解析此文件,看进一步动作);fstab
(主要包含一些挂载的文件系统,如sys proc
)init.rd/rcS
(可存放一些可执行脚本,配合inittab
使用)proc
:proc
文件系统挂载点sys
:sys
文件系统挂载点tmp
:tmp
文件系统挂载点dev
: 设备文件lib
: 库文件目录(如果busybox采用动态链接库,则需要将交叉编译链的库文件拷这里)
注意:本文采用的是静态链接库,所以不需要拷贝库到这个 lib
目录下。但是静态链接库,会占用更多的内存。
去 dev
目录下静态创建如下节点:
sudo mknod -m 666 tty1 c 4 1
sudo mknod -m 666 tty2 c 4 2
sudo mknod -m 666 tty3 c 4 3
sudo mknod -m 666 tty4 c 4 4
sudo mknod -m 666 console c 5 1
sudo mknod -m 666 null c 1 3
console
和 null
是必须的,如果没有则会报错。
etc/inittab
文件内容如下,可参考 busyboxdir/examples/inittab
编写:
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
etc/fstab
文件内容如下,主要目的是指明一些文件系统挂载点:
#device mount-point type option dump fsck order
proc /proc proc defaults 0 0
#temps /tmp rpoc defaults 0 0
none /tmp ramfs defaults 0 0
sysfs /sys sysfs defaults 0 0
mdev /dev ramfs defaults 0 0
etc/init.d/rcS
文件内容如下,inittab
第一条指明了从 rcS
中去执行脚本
#!/bin/sh
mount -a
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
/sbin/mdev -s # 根据/etc/mdev.conf中的配置进行生成设备节点
mount -a
最后修改 rcS
的权限:
chmod 777 etc/init.d/rcS
创建根文件系统
使用 dd
命令创建一个空白的 512M
(根据实际情况)文件:
dd if=/dev/zero of=rootfs.ext3 bs=1M count=512
将该空白文件格式化为 ext3
格式(内核默认支持文件系统,如果使用其他需要配置内核):
mkfs.ext3 rootfs.ext3
将该空白文件,挂载在一个目录下:
mkdir rootfs
sudo mount -o loop rootfs.ext3 ./rootfs
将 busybox
构建的根文件系统拷贝到挂载点下,然后再卸载:
sudo cp -rf busybox-1.35.0/_install/* ./rootfs
sudo umount rootfs
使用QEMU启动ARM32 Linux内核
执行以下命令启动:
qemu-system-arm \
-M vexpress-a9 \
-kernel ./linux-4.4.240/build/arch/arm/boot/zImage \
-nographic \
-m 512M \
-smp 4 \
-dtb ./linux-4.4.240/build/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-sd rootfs.ext3 \
-append "init=/linuxrc root=/dev/mmcblk0 rw rootwait earlyprintk console=ttyAMA0"
上述命令:
-M
指定了目标板;-kernel
指定了linux内核镜像;-nographic
指定无图形显示;-m 512M
指定了运行内存大小;-smp
指定4核;-sd
指定了外部有1个sd
卡,卡内是rootfs.ext3
镜像文件;-dtb
指定了设备树文件;-append
指定了bootargs
;bootargs
中init=/linuxrc
指定了init
进程是根文件系统下的linuxrc
(busybox生成),root=/dev/mmcblk0
指定了根文件系统为sd
卡,console
指定了ttyAMA0
,即控制台。
支持文件共享
1.宿主机 创建一个用于文件共享的目录 shared
:
mkdir shared
2.使用以下命令启动 qemu
:
qemu-system-arm \
-M vexpress-a9 \
-m 512M \
-kernel /home/wurusai/workspace/linux-4.4.240/arch/arm/boot/zImage \
-dtb /home/wurusai/workspace/linux-4.4.240/arch/arm/boot/dts/vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-drive file=rootfs.ext3,format=raw,if=sd,readonly=off \
-fsdev local,id=host0,path=./shared,security_model=passthrough \
-device virtio-9p-device,fsdev=host0,mount_tag=host0
3.客户机 创建一个用于共享的文件夹 /mnt/shared
,并且挂载:
mkdir -p /mnt/shared
mount -t 9p -o trans=virtio host0 /mnt/shared/
最终效果如下:
宿主机:
客户机:
解决文件共享的问题
目前文件共享有一个问题——就是每次启动 qemu
都需要手动来挂载共享目录 /mnt/shared
。为了解决这个问题,可以把这个挂载命令写在脚本 S90_mount_shared
里面,然后放在 /etc/init.d/
目录下,这样每次启动这个脚本就会被自动执行,不需要手动挂载目录了。
详情参考:嵌入式Linux设置开机自动运行程序(基于BusyBox init)
/etc/inittab
:
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
tty2::askfirst:/bin/sh
tty3::askfirst:/bin/sh
tty4::askfirst:/bin/sh
# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
/etc/init.d/rcS
:
#!/bin/sh
mount -a
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
/sbin/mdev -s # 根据/etc/mdev.conf中的配置进行生成设备节点
mount -a
# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i start
;;
esac
done
脚本文件 /etc/init.d/S90_mount_shared
:
#!/bin/sh
umask 077
start() {
echo "Mounting shared directory successfully!"
mount -t 9p -o trans=virtio host0 /mnt/shared
}
stop() {
echo "Umounting /mnt/shared successfully!"
umount /mnt/shared
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart|reload)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
最终启动效果如图: