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子系统,后面我们进行尝试。

posted @ 2024-07-05 17:07  小满的博客  阅读(38)  评论(0编辑  收藏  举报