QEMU搭建完整嵌入式系统
芯片选择
当前世面上嵌入式开发还是以32位为主,我们选择vexpress开发板,一步一步构建出一个完整的嵌入式系统。
编译arm版本QEMU
../configure --target-list=arm-softmmu
make -j16
make install
重新配置后编译会把旧的删掉,执行install将产物安装
安装arm工具链
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf
sudo apt-get install g++-arm-linux-gnueabihf
arm-linux-gnueabihf-gcc -v
arm-linux-gnueabihf-g++ -v
编译uboot
http://ftp.denx.de/pub/u-boot/
提前安装依赖
sudo apt-get install libncurses5-dev
开始编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- vexpress_ca9x4_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
编译完成,验证uboot
qemu-system-arm -M vexpress-a9 -kernel u-boot -nographic -m 512M
qemu退出方法:Ctrl + a
,然后按 x
键。
编译busybox
make CROSS_COMPILE=arm-linux-gnueabihf- menuconfig -j16
选中静态链接
Settings
[*] Build static binary (no shared libs)
执行编译
make CROSS_COMPILE=arm-linux-gnueabihf- install -j16
file _install/bin/busybox
编译linux kernel
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.9.7.tar.xz
提前安装依赖
sudo apt-get install lzop
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=build distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=build vexpress_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=build menuconfig
打开RAM block device support 选项,将配置保存为自己的配置vex_my_defconfig方便后续修改
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=build distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=build vex_my_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- O=build all -j16
/build/arch/arm/boot
下存放着编译结果
build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb
为使用的设备树
结合之前我们制作的initrd,验证kernel
qemu-system-arm -nographic \
-M vexpress-a9 \
-smp 4 -m 512M \
-kernel source/linux-6.9.7/build/arch/arm/boot/zImage \
-dtb source/linux-6.9.7/build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
-append "nokaslr root=/dev/ram init=/init console=ttyAMA0" \
-initrd source/initrd.ext4
有了initrd,方便后续加入周边设备的调试.
运行后有报错
Run /init as init process
request_module: modprobe binfmt-464c cannot be processed, kmod busy with 50 threads for more than 5 seconds now
Kernel panic - not syncing: Requested init /init failed (error -8).
是因为编译的busybox产物架构不是ARM的,重新编译制作initrd即可
file busybox
busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), statically linked, BuildID[sha1]=fb4dddca98fd6d4af6361dda368ead8ce0088e80, for GNU/Linux 3.2.0, stripped
重新运行后,可以正常进入console.
initrd的大小是有限制的,如果想在板子内加载更多的东西,可以换成挂载sd卡的方式加载根目录,我们这里就不演示了。
不过不论是initrd还是sd卡,都需要重复的去制作根目录,比较麻烦,我们开启nfs,直接加载文件夹,这样更方便开发。
通过NFS挂载根文件系统
配置NFS
使用如下命令安装 NFS 服务:
sudo apt-get install nfs-kernel-server rpcbind
打开 nfs 配置文件
sudo vi /etc/exports
添加如下所示内容:
/home/xxxx/linux/nfs *(rw,sync,no_root_squash)
重启 NFS 服务:
sudo /etc/init.d/rpcbind restart
sudo /etc/init.d/nfs-kernel-server restart
重新编译内核,并在menuconfig下开启NFS4支持。
位置:File System -> Network File Systems->NFS client support for NFS version 4
配置qemu网络桥接
加载驱动
modprobe bridge
lsmod |grep bridge
安装依赖
sudo apt install -y bridge-utils uml-utilities
创建网桥
sudo brctl addbr virbr0
sudo brctl stp virbr0 on
brctl show
创建一个名为 tap0 的TAP设备
sudo ip tuntap add dev tap0 mode tap
绑定 tap设备(名为 tap0 的TAP设备) 和 网桥,进入/usr/local/etc
,创建up和down的脚本
sudo vim qemu-ifup
#! /bin/sh
switch=virbr0
ifconfig $1 up
#ip link set $1 up
brctl addif ${switch} $1
sudo vim qemu-ifdown
switch=virbr0
brctl delif ${switch} $1
ifconfig $1 down
#ip link set $1 down
#tunctl -d $1
创建完成后赋予可执行权限
绑定 虚拟网桥 和 真实网卡
// ubuntu2204 的版本
// 注意缩进
// TODO 将 ens33 换为你的真实网卡名,并设置IP地址,关闭dhcp
// TODO 修改 macaddress 为实际的
// TODO IP修改为自己的网段
$ cat /etc/netplan/virbr0.yaml
network:
version: 2
renderer: networkd
ethernets:
ens33:
dhcp4: false
dhcp6: false
addresses:
- 172.16.193.129/24
nameservers:
addresses:
- 172.16.193.2
bridges:
virbr0:
macaddress: 52:f1:13:a4:ef:ca
dhcp4: false
dhcp6: false
addresses:
- 172.16.193.130/24
routes:
- to: default
via: 172.16.193.2
nameservers:
addresses:
- 172.16.193.2
interfaces:
- ens33
启用配置
sudo netplan apply
重启 NetworkManager 服务
sudo systemctl restart NetworkManager
启动qemu-system-arm
sudo qemu-system-arm -nographic \
-M vexpress-a9 \
-smp 4 -m 512M \
-kernel source/linux-6.9.7/build/arch/arm/boot/zImage \
-dtb source/linux-6.9.7/build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
-append "nokaslr root=/dev/ram init=/init console=ttyAMA0" \
-initrd source/initrd.ext4 \
-net nic \
-net tap,ifname=tap0
设置IP
ifconfig eth0 172.16.193.131 netmask 255.255.255.0
尝试ping宿主ubuntu,已经可以通了,但是仅能访问通宿主ubuntu,还不能解析域名,后面等完整的rootfs建立后再优化
ping 172.16.193.129
以nfs方式启动
我们将initrd.ext4内的全部文件拷贝到nfsrootfs文件夹中,然后启动
sudo qemu-system-arm -nographic \
-M vexpress-a9 \
-m 512M \
-kernel source/linux-6.9.7/build/arch/arm/boot/zImage \
-dtb source/linux-6.9.7/build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
-net nic \
-net tap,ifname=tap0 \
-append "root=/dev/nfs rw nfsroot=172.16.193.129:/home/lichong/Study/vexpress/source/nfsrootfs,proto=tcp,nfsvers=4,nolock init=/linuxrc console=ttyAMA0 ip=172.16.193.131"
进去的速度很快,也可以看到我们去掉了-initrd source/initrd.ext4
参数
IP-Config: Guessing netmask 255.255.0.0
IP-Config: Complete:
device=eth0, hwaddr=52:54:00:12:34:56, ipaddr=172.16.193.131, mask=255.255.0.0, gw=255.255.255.255
host=172.16.193.131, domain=, nis-domain=(none)
bootserver=255.255.255.255, rootserver=172.16.193.129, rootpath=
clk: Disabling unused clocks
ALSA device list:
#0: ARM AC'97 Interface PL041 rev0 at 0x10004000, irq 37
VFS: Mounted root (nfs4 filesystem) on device 0:20.
Freeing unused kernel image (initmem) memory: 1024K
Run /linuxrc as init process
Welcome to ARM64 Linux
Please press Enter to activate this console.
这下修改rootfs的东西,就不需要再制作文件了,少了一步,很方便。
制作完全体rootfs
buildroot方式
busybox 构建的根文件系统不齐全,很多东西需要我们自行添加,比如 lib 库文件。在我们后面的驱动开发中很多第三方软件也需要我们自己去移植,这些第三方软件有很多又依赖其他的库文件,导致移植过程非常的繁琐。本章我们来学习一下另外一种实用的根文件系统构建方法,那就是使用 buildroot 来构建根文件系统。
官网地址为 https://buildroot.org/
使用make menuconfig
配置选项时,如果退格键无法删除内容,可以加按Ctrl
键,即使用Ctrl+backspace
组合键。
配置并选择常用的工具,示例如下
Target options
Target Architecture (ARM (little endian))
Target Architecture Variant (cortex-A9) //CPU架构
Target ABI (EABI)
Floating point strategy (Soft float) //没有FPU的选择软浮点
ARM instruction set (ARM)
Target Binary Format (ELF)
Toolchain
Toolchain type (Buildroot toolchain) //使用内置编译器
C library (glibc)
Kernel Headers (Linux 6.6.x kernel headers)
GCC compiler Version (gcc 12.x)
Enable C++ support
Build cross gdb for the host
System configuration
(xiaoman) System hostname
(Welcome to mylinux) System banner //欢迎语
Init system (systemd) //使用systemd作为init进程
(123) Root password //设置密码
/bin/sh (bash) //选择shell进程
Target packages
→ Compressors and decompressors
[*] gzip
[*] unzip
→ Debugging, profiling and benchmark
[*] gdb
→ Text editors and viewers
[*] vim
Target packages
→ Networking applications
[*] iproute2
[*] net-tools
[*] netstat-nat
[*] openssh
[*] socat
[*] tftpd
[*] wget
Kernel和Bootloaders不选中
安装依赖
sudo apt-get install cmake
如果我们在 buildroot 中的 toolchain 指定外部编译工具为之前在 Ubuntu 上面 apt-get 的交叉编译器,那么编译的时候则会出现错误信息:
Distribution toolchains are unsuitable for use by Buildroot,
as they were configured in a way that makes them non-relocatable,
and contain a lot of pre-built libraries that would conflict with
the ones Buildroot wants to build.
这是因为 Ubuntu 得到的交叉编译器被配置成不可重定位的,而且包含了一些与 buildroot 相冲突的库
所以我们要自己下载交叉编译工具或者让 buildroot 自动下载。为了方便让buildroot 自动下载,当然也可以自己行下载然后选择使用外部交叉编译器。
buildroot的交叉工具链位置 output/host
文件下,后续编译应用程序可以使用此工具链。
运行sudo make
,等待编译完成,我的电脑是AMD 6800H用时约一个小时。后面修改了配置直接编译,就会快很多。
结束后,目标文件在output/images/rootfs.tar
,拷贝出来解压
tar xvf rootfs.tar -C nfsrootfs
sudo qemu-system-arm -nographic \
-M vexpress-a9 \
-m 512M \
-kernel source/linux-6.9.7/build/arch/arm/boot/zImage \
-dtb source/linux-6.9.7/build/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
-net nic \
-net tap,ifname=tap0 \
-append "root=/dev/nfs rw nfsroot=172.16.193.129:/home/lichong/Study/vexpress/source/nfsrootfs,proto=tcp,nfsvers=4,nolock console=ttyAMA0 ip=172.16.193.131"
相比busybox方式,去掉了init=/linuxrc
参数,直接调起systemd
[ OK ] Reached target Basic System.
Starting D-Bus System Message Bus...
[ OK ] Started Serial Getty on ttyAMA0.
[ OK ] Reached target Login Prompts.
Welcome to mylinux
xiaoman login: root
Password:
#
#
这下一个完整的linux系统就构建出来了,需要什么库可以通过buildroot的配置来选择,十分方便。
优化命令行,打开/etc/profile
文件 ,将下面的屏蔽
if [ "${PS1-}" ]; then
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
# The file bash.bashrc already sets the default PS1.
# PS1='\h:\w\$ '
if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fi
else
if [ "$(id -u)" -eq 0 ]; then
PS1='# '
else
PS1='$ '
fi
fi
fi
新增
PS1='[\u@\h]:\w$:'
export PS1
重启后即可,最终效果:
[ OK ] Started Serial Getty on ttyAMA0.
[ OK ] Reached target Login Prompts.
Starting OpenSSH server daemon...
random: crng init done
Welcome to mylinux
xiaoman login: root
Password:
[root@xiaoman]:~$:
[root@xiaoman]:/$:cd /etc/
[root@xiaoman]:/etc$:
移植 Debian 文件系统
buildroot方式制作的文件系统快捷灵活,可方便的进行裁剪和增加,适合小型嵌入式系统。如果想要大而全的文件系统,我们使用Debian 文件系统来进行移植,当然也可以将 Debian 文件系统裁剪为小型嵌入式系统。
可以参考网上的教程:移植 Debian 文件系统
总结
上面详细讲解了如何搭建一个完整的嵌入式linux系统。软件验证后,按照真实的开发板,就是应该全部烧录到存储介质中。这部分依据不同的芯片有不同的启动要求,这里就不再详加讨论。
这个完整的嵌入式系统构建出来后,就可以进行驱动开发,比如OpenBMC开发——qemu添加新设备及设备注册至I2C子系统,后面我们进行尝试。