qemu 嵌入式linux 开发环境搭建
主要记录大致步骤.
qemu 安装
wget https://download.qemu.org/qemu-9.2.1.tar.xz
tar --xz --get --file qemu-9.2.1.tar.xz
cd qemu-9.2.1
./configure
make -j$(nproc) # $(nproc) 获取cpu核心数
sudo make install
qemu-system-arm -M help # 查看支持的板子
交叉编译工具链安装
sudo apt-get install gcc-arm-linux-gnueabi
怎么选择交叉编译工具链: https://www.cnblogs.com/wxishang1991/p/5322499.html
u-boot 移植
git clone git://git.denx.de/u-boot.git
ls configs/ | grep vexpress
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- vexpress_ca9x4_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j$(nproc)
linux 内核移植
清理编译和配置文件
make mrproper
#下载解压内核源码
#通常来说内核源码分别可以从官方,芯片厂商,开发板厂商处获取.
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.13.1.tar.xz
tar --get --file linux-6.13.1.tar.xz
# 生成默认.config文件, 把默认配置文件复制到kernel顶层目录
# 我使用的是vexperss-a9开发板,直接找找有没有想关配置文件
ls arch/arm/configs/ | grep vexpress
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- vexpress_defconfig
#配置内核,通过menuconfig修改.config文件
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
#编译内核
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- -j $(nproc)
#仿真
#仿真还需要设备树文件, Linux kernel 里有一些默认的设备树文件
ls arch/arm/boot/dts/arm | grep vexpress
qemu-system-arm -M vexpress-a9 -m 512M \
-kernel arch/arm/boot/zImage \
-dtb arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
-append "console=ttyAMA0" \
-nographic
#此时内核可以运行一段但会因为没有文件系统停下来.
# Unable to mount root fs on unknown-block(0,0)
不知道为什么 make menuconfig 后.config变成了x86 编译器字段也变成了gcc而不是交叉编译工具.
因为开始make后面没有带环境变量所以相当于是修改了.正常是读取顶层.config的
linux内核的配置选项很多, 通常都是基于默认或硬件厂商提供的配置文件, 然后根据需求进行修改.
编译过程中可能会因为有些依赖没有安装而报错, 根据提示安装缺少的依赖即可.
使用busybox制作根文件系统
- 下载安装busybox
git clone git://busybox.net/busybox.git
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- defconfig
#选择静态编译// 有其他的需求在这一步进行配置,可以参考上面的链接
make menuconfig
# 勾选 Busybox Setting-> Build Options-> [*] Build static binary (no shared libs)
make -j$(nproc)
- 制作根文件系统
目录 | 说明 | 补充 |
---|---|---|
linuxrc | 第一个执行的用户程序, 提供操作界面 | 必须存在, busybox生成 |
bin | 所有用户都可以使用的、基本的命令 | 必须存在, busybox创建并集成一些命令 |
sbin | 基本的系统命令 | 必须存在, busybox创建并集成一些命令 |
usr | 用户目录 | 可以删除, busybox创建并集成一些命令 |
etc | 存放配置文件, 被 /linuxrc 所调用执行 | 必须存在 |
lib | 存放的是当前操作系统中的动态和静态链接库文件 | 必须存在 |
dev | 设备文件 | 必须存在 |
sys | 虚拟文件系统, 不可省略, 但是只要创建了空文件夹即可 | 必须存在 |
proc | 虚拟文件系统, 不可省略, 但是只要创建了空文件夹即可 | 必须存在 |
mnt | 用来挂载 | 可以删除 |
- 创建根文件系统目录
#!/bin/bash
rm -rf rootfs/
echo "----Create rootfs directons"
# 创建文件系统目录
mkdir -p rootfs/{bin,sbin,usr,dev,etc,lib,proc,sys,mnt,root}
mkdir -p rootfs/dev/pts
# 复制busybox生成的文件
echo "----Copy busybox{bin,linuxrc,sbin,usr}"
cp busybox-*/_install/* rootfs/ -arf #保留符号链接因为busybox生成的命令都是符号链接到busybox.bin
# 创建etc/inittab文件
echo "----Create etc/inittab"
cat <<EOL > rootfs/etc/inittab
#/etc/inittab # 井号是注释
::sysinit:/etc/init.d/rcS # 系统启动以后运行 /etc/init.d/rcS 这个脚本文件
console::askfirst:-/bin/sh # 将 console 作为控制台终端,也就是 ttymxc0。
::restart:/sbin/init # 重启的话运行/sbin/init。
::ctrlaltdel:-/sbin/reboot # 按下 ctrl+alt+del 组合键的话就运行 /sbin/reboot(重启系统)
::shutdown:/bin/umount -a -r # 关机的时候执行 /bin/umount,也就是卸载各个文件系统
::shutdown:/sbin/swapoff -a # 关机的时候执行 /sbin/swapoff,也就是关闭交换分区。
EOL
# 创建/etc/init.d/rcS文件
echo "----Create etc/init.d/rcS"
mkdir -p rootfs/etc/init.d
cat <<EOL > rootfs/etc/init.d/rcS
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin:
LD_LIBRARY_PATH=/lib:/usr/lib
export PATH LD_LIBRARY_PATH # 导出上面这些环境变量
mount -a # 挂载所有的文件系统,需存在/etc/fstab 文件
EOL
chmod +x rootfs/etc/init.d/rcS
# 创建etc/fstab文件
echo "----Create etc/fstab"
cat <<EOL > rootfs/etc/fstab
proc /proc proc defaults 0 0 #用于暴露内核信息,驱动编程需要/proc/devices
sysfs /sys sysfs defaults 0 0 #用于暴露系统硬件和内核对象
devtmpfs /dev devtmpfs defaults 0 0
EOL
# 复制动态库文件
echo "----Add dynamic library"
#直接复制交叉编译工具lib目录下的动态库
cp /usr/arm-linux-gnueabi/lib/*.so* rootfs/lib/ -arf
arm-linux-gnueabi-strip rootfs/lib/*.so* #去除动态库的调试信息
# 创建设备节点
echo "----make node: dev/console dev/null"
sudo mknod -m 600 rootfs/dev/console c 5 1
sudo mknod -m 600 rootfs/dev/null c 1 3
- 把配置好的文件系统打包成ext3文件系统
#!/bin/bash
rm -f a9rootfs.ext3
dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=64
mkfs.ext3 a9rootfs.ext3
mkdir -p tmpfs
chmod 777 tmpfs
sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
sudo cp -r rootfs/* tmpfs/
sudo umount tmpfs
rm -rf tmpfs
- 仿真
qemu-system-arm -M vexpress-a9 -m 512M \
-kernel /home/wuuu/vmDevEnv/a9_linux/linux-6.13.1/arch/arm/boot/zImage \
-dtb /home/wuuu/vmDevEnv/a9_linux/linux-6.13.1/arch/arm/boot/dts/arm/vexpress-v2p-ca9.dtb \
-nographic \
-append "root=/dev/mmcblk0 rw console=ttyAMA0" \
-sd a9rootfs.ext3
根文件系统给linux提供了什么?
- 一些基本的命令
- 一些基本的库
- 一些配置文件
- 一些设备文件
- 一些启动脚本
补充1 网络挂载
服务端
- 安装配置NFS服务
sudo apt update
sudo apt install nfs-kernel-server
- 配置NFS服务
sudo vim /etc/exports
# 添加如下内容
/home/wuuu/vmDevEnv/a9_linux/rootfs *(rw, sync, no_root_squash, no_subtree_check)
# 格式: /path/to/share 允许访问的ip(rw,sync,no_root_squash,no_subtree_check)
# rw:允许读写。
# sync:同步写入。
# no_root_squash:允许远程 root 用户具有与本地 root 用户相同的权限
# no_subtree_check # 访问子目录时不进行权限检查
sudo exportfs -ra # 重新加载配置文件
- 启动NFS服务
sudo systemctl start nfs-kernel-server
sudo systemctl enable nfs-kernel-server #开机启动
# 查看NFS服务状态
sudo systemctl status nfs-kernel-server
#查看export的NFS共享目录
sudo showmount -e
qemu网络配置
虚拟机需要和宿主机在同一网络下才能访问到NFS服务, 所以使用桥接模式.
- 宿主机网络配置
ifconfig # 查看宿主机的ip和mask
# eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
# inet 172.24.29.97 netmask 255.255.240.0 broadcast 172.24.31.255
route # 查看宿主机的网关
# Destination Gateway Genmask Flags Metric Ref Use Iface
# default DESKTOP-GFVJBIC 0.0.0.0 UG 0 0 0 eth0
# 172.24.16.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0
# 创建一个桥接设备br0
sudo ip link add br0 type bridge
# 创建一个tap设备tap0, 给qemu使用, qemu启动的时候把虚拟机的网卡连接到tap0上
sudo ip tuntap add dev tap0 mode tap
# 把tap0添加到br0上
sudo ip link set dev tap0 master br0
# 把宿主机的网卡eth0添加到br0上
sudo ip link set dev eth0 master br0
# 启动br0和tap0
sudo ip link set dev br0 up
sudo ip link set dev tap0 up
# 把eth0的ip解绑, 然后绑到br0上
sudo ip address delete $ip dev eth0 # 解绑的时候只用ip
sudo ip address add ${ip/mask} dev br0 # 给br0绑定的时候还有掩码, 格式:172.24.29.97/20, /后面的20就是掩码,表示前20位是1,对应的mask是255.255.240.0
# 添加路由
sudo ip route add default via $ROUTE dev br0 #根据上面的route显示的内容这里$ROUTE应该是172.24.16.0
#--------------------------桥接网络配置完成---------------------------------------------------------
# 补充命令
# 删除br0和tap0
sudo ip link delete br0 type bridge
sudo ip tuntap delete dev tap0 mode tap
- qemu配置
在启动项里添加
简单解释下这个选项, 现代qemu把设备分为前端和后端, -netdev 是创建一个后端,-netdev tap,id=net0,ifname=tap0,script=no,downscript=no -device virtio-net-device,netdev=net0
ifname=tap0
表示这个后端和宿主的tap0连接在一起. -device 是创建一个前端(虚拟机内的虚拟设备),netdev=net0
表示这个前端和后端net0连接在一起.
客户端
- 配置内核支持NFS
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- menuconfig
# File systems -> Network File Systems -> NFS client support
# ENable NFS version 4 sup
-
配置虚拟机ip和宿主机在一个子网内
在启动脚本rcS中添加ifconfig eth0 172.24.29.21
, 设置eth0的ip.配置好ip后, 可以通过
ping -c 4 $ip
来测试是否能ping通宿主机. 然后尝试挂载NFS服务.
mount -t nfs $ip:/path/to/share /mnt
# 如遇到下面报错
# svc: failed to register lockdv1 RPC service (errno 101).
# 使用 -o nolock 选项
mount -t nfs -o nolock 172.24.29.97:/home/wuuu/vmDevEnv/a9_linux/rootfs /mnt
到此满足我目前的需求了, 挂载nfs作为根目录以后再研究.