Ubuntu虚拟机中使用QEMU搭建ARM64环境

Ubuntu虚拟机中使用QEMU搭建ARM64环境

通过本实验学习如何编译一个 ARM64 版本的内核 image,并且在QEMU 上运行起来。

一、安装aarch64交叉编译工具

搭建QEMU的模拟环境首先需要下载安装对应架构的交叉编译工具链(这里是arm64架构):

sudo apt-get install gcc-aarch64-linux-gnu
sudo apt-get install libncurses5-dev  build-essential git bison flex libssl-dev

image-20250307215602049

image-20250307215652241安装完成之后查看版本说明安装完成:

$ aarch64-linux-gnu-gcc -v

image-20250307215742797

二、安装QEMU

在终端中输入 sudo apt install qemu-system-arm安装

image-20250307215809308

安装完成后,输入 qemu-system-aarch64 --version 来查看 qemu版本

image-20250307215908572

三、制作根文件系统

1、根文件系统简介

Linux的根文件系统一般也叫做 rootfs,Linux的根文件系统更像是一个文件夹或者叫做目录,在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是Linux运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。

根文件系统的“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根” ,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中。

在构建根文件系统之前,先来看一下根文件系统里面都有些什么内容,根文件系统的目录名字为‘/’ ,就是一个斜杠。下面以Ubuntu为例,一些常用的子目录介绍如下表示:

image-20250307201933510

2、BusyBox构建根文件系统

1)下载BusyBox源码

BusyBox是一个集成了大量的Linux命令(如ls、mv、ifconfig 等命令)和工具的软件。借助BusyBox,进行配置和编译,就可以方便的构建一个嵌入Linux平台所需要的根文件系统。

可在BusyBox官网 https://busybox.net/ 下载源码。

将压缩包拖拽到Ubuntu虚拟机中的桌面或文件夹中,并解压

image-20250307222203393

tar -vxjf busybox-1.37.0.tar.bz2

解压后的文件如下:

image-20250307222336805

2)制定编译工具链

cd busybox-1.36.1
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
//检测是否配置成功
echo $ ARCH
echo $CROSS_COMPILE

image-20250307222735565

3)配置编译BusyBox

busybox中文字符支持:若直接编译busybox,使用串口工具时是不支持中文显示的,会显示为“?” ,可修改源码,取消 busybox对中文显示的限制

打开文件/libbb/printable_string.c,将函数printable_string()中的部分程序注释掉,修改后的函数内容如下:

/********** printable_string.c代码段 **********/
const char* FAST_FUNC printable_string(uni_stat_t *stats, const char *str)
{
	char *dst;
	const char *s;

	s = str;
	while (1) {
		......
		if (c < ' ')
			break;
		/* 注释掉下面这个两行代码,禁止字符大于0X7F以后 break */
		/* if (c >= 0x7f)   
			 break; */
		s++;
	}

#if ENABLE_UNICODE_SUPPORT
	dst = unicode_conv_to_printable(stats, str);
#else
{
	char *d = dst = xstrdup(str);
	while (1) {
	unsigned char c = *d;
	if (c == '\0')
		break;
	/* 修改下面代码,禁止字符大于0X7F以后输出‘?’ */
	/* if (c < ' ' || c >= 0x7f) */
	if( c < ' ')
		*d = '?';
	d++;
}
......
#endif
	return auto_string(dst);
}

接着打开文件/libbb/unicode.c,修改如下内容:

/********** unicode.c代码段 **********/
static char* FAST_FUNC unicode_conv_to_printable2(uni_stat_t *stats, const char *src, unsigned width, int flags)
{
	char *dst;
	unsigned dst_len;
	unsigned uni_count;
	unsigned uni_width;

	if (unicode_status != UNICODE_ON) {
		char *d;
		if (flags & UNI_FLAG_PAD) {
			d = dst = xmalloc(width + 1);
			......
				/* 修改下面一行代码 */
				/* *d++ = (c >= ' ' && c < 0x7f) ? c : '?'; */
				*d++ = (c >= ' ') ? c : '?';
				src++;
			}
			*d = '\0';
		} else {
			d = dst = xstrndup(src, width);
			while (*d) {
				unsigned char c = *d;
				/* 修改下面一行代码 */
				/* if (c < ' ' || c >= 0x7f) */
				if(c < ' ')
					*d = '?';
				d++;
			}
		}
		......
		return dst;
	}
......
return dst;
}

  • 配置busybox:有以下几种配置选项,一般使用默认配置即可

– defconfig:缺省配置,也就是默认配置选项
– allyesconfi:全选配置,即选中 busybox 的所有功能
– allnoconfig:最小配置

make defconfig  	#使用默认配置
make menuconfig		#打开图形化配置界面

在这里插入图片描述

设置Settings -> Build static binary (no shared libs)

image-20250307232958795

设置Settings -> Support Unicode,使能busybox的unicode编码以支持中文

image-20250307231300248

  • 编译busybox:配置好busybox以后就可以编译了,输入如下命令
make
make install CONFIG_PREFIX=/home/xlq/linux/rootfs
#CONFIG_PREFIX指定编译结果的存放目录

image-20250307233117424

编译完成以后,busybox的所有工具和文件就会被安装到rootfs目录中,如下图;rootfs目录下有bin、sbin和usr三个目录,以及linuxrc文件。

image-20250307233209439

4)创建需要的目录

cd  ~/linux/rootfs
mkdir dev etc lib sys proc tmp var home root mnt
(a) etc目录更新
  • 创建 profile 文件,添加下面内容
#!/bin/sh
export HOSTNAME=user
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH

用nano profile打开新建的文件后,Ctrl+O打开写入后直接回车,再点击Ctrl+x退出终端

回到文件夹下会出现新建的一个profile空白文件,将上述内容复制粘贴到该空白文件下即可。

后续创建操作皆可如此

  • 创建 inittab 文件,添加下面内容
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
  • 创建 fstab 文件,添加下面内容,指定挂载的文件系统
#device  mount-point    type     options   dump   fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0
  • 创建init.d目录
mkdir  init.d
  • 在init.d下创建 rcS文件,添加下面内容
cd init.d

mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
  • 添加权限
chmod 777 rcS
  • 利用tree命令查看etc下目录结构如下所示

image-20250307235749530

image-20250307235813055

(b) dev目录下的必要文件
cd dev
sudo mknod console c 5 1

image-20250308000013336

输出下面命令检查是否创建了console的设备文件:

ls -l /dev/console

image-20250308000143214

© lib目录下的必要文件

为了支持动态编译的应用程序的执行,根文件系统需要支持动态库,所以我们添加arm64相关的动态库文件到lib下

cd lib
cp /usr/aarch64-linux-gnu/lib/*.so* -a .

检查当前目录是否包含了.so文件:

image-20250308000449715

四、编译内核源码

1、下载源码

下载Linux的内核源码点击下载

很多教程是直接使用命令行下载,但我最初直接采用命令行方式下载Ubuntu内存爆红并且下载失败了,后面改用直接从网站下载比较稳妥。

在这里插入图片描述

同BusyBox放到Ubuntu合适位置进行解压

tar xvf linux-6.13.5.tar.xz

2、指定编译工具

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

3、将根文件系统放到源码根目录

cd linux-6.13.5
sudo cp  ~/linux/rootfs rootfs_arm64 -a

image-20250308002220347

4、配置生成.config

make defconfig
make menuconfig

添加hotplug支持

    Device Drivers                                                                                                 
        -> Generic Driver Options                                                                                             
            -> Support for uevent helper                                                                                                                
                (/sbin/hotplug) path to uevent helper

添加initramfs支持

General setup --->
     
     [*]Initial RAM filesystem and RAM disk(initramfs/initrd) support
        (_install_arm64) Initramfs souce file(s)

Virtual address space配置

    Kernel Features  ---> 
      	Page size(4KB)  --->
        Virtual address space size(48-bit)--->

5、编译

make all -j8

五、启动QEMU

1、创建共享文件目录

​ 在内核源码目录下创建目录

mkdir kmodules

2、运行QEMU模拟器

​ 在内核源码目录下执行下面命令

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt  -m 1024 -smp 4 -kernel arch/arm64/boot/Image --append "rdinit=/linuxrc root=/dev/vda rw console=ttyAMA0 loglevel=8"  -nographic  --fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none  -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount

— machine virt:使用virt机器类型。
— cpu cortex-a57:使用Cortex-A57 CPU模型。
— -m 1024:设置虚拟机内存大小为1024MB。
— -smp 4:设置虚拟机使用4个CPU核心。
— -kernel arch/arm64/boot/Image:指定Linux内核镜像的路径。
— --append “rdinit=/linuxrc root=/dev/vda rw console=ttyAMA0 loglevel=8”:指定内核启动参数,其中rdinit指定init程序的路径,root指定根文件系统的设备,rw表示以读写模式挂载根文件系统,console指定控制台设备,loglevel指定内核日志级别。
— -nographic:禁用图形界面,使用纯文本控制台。
— --fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none:创建一个本地文件系统设备,其中id指定设备ID,path指定设备挂载的本地路径,security_model指定安全模型。
— -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount:将本地文件系统设备挂载到虚拟机中,其中fsdev指定设备ID,mount_tag指定设备挂载的标签。

启动成功后会打印下面内容,点击Enter进入控制台

image-20250308125559937

image-20250308125646865

退出QEMU模拟器

poweroff

4、编译一个简单的内核模块并在QEMU上运行

  • 在根目录下创建一个文件夹module_test,并编写一个简单的hello.c代码

    // 包含内核模块编程所需的头文件
    #include <linux/init.h>  // 包含模块初始化和退出函数的宏
    #include <linux/module.h>  // 包含模块相关的宏和函数
    #include <linux/kernel.h>  // 包含内核打印函数 printk 的头文件
    
    // 模块初始化函数
    // 当模块被加载到内核时,此函数会被调用
    static int __init test_init(void)
    {
        // 在内核日志中打印 "hello world!"
        printk("hello world!\n");
        
        // 返回 0 表示初始化成功
        return 0;
    }    
    
    // 模块退出函数
    // 当模块从内核中卸载时,此函数会被调用
    static void __exit test_exit(void)
    {
        // 在内核日志中打印 "hello exit!"
        printk("hello exit!\n");
    }
    
    // 注册模块的初始化函数
    // 当模块被加载时,test_init 函数会被调用
    module_init(test_init);
    
    // 注册模块的退出函数
    // 当模块被卸载时,test_exit 函数会被调用
    module_exit(test_exit);
    
    // 声明模块的许可证
    // GPL 是 GNU 通用公共许可证,表示这是一个开源模块
    MODULE_LICENSE("GPL");
    
  • 再编写Makefile文件

# 设置目标架构为 ARM64
export ARCH=arm64

# 设置交叉编译工具链前缀为 aarch64-linux-gnu-
export CROSS_COMPILE=aarch64-linux-gnu-

# 定义内核源码目录
KERNEL_DIR ?= /home/xlq/linux-6.13.5

# 定义要编译的内核模块目标文件
# obj-m 表示将 hello.c 编译为内核模块 hello.ko
obj-m := hello.o

# 默认目标:编译内核模块
modules:
	# 调用内核源码目录的 Makefile,编译当前目录下的模块
	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules

# 清理目标:删除编译生成的文件
clean:
	# 调用内核源码目录的 Makefile,清理当前目录下的生成文件
	$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean

# 安装目标:将编译好的内核模块复制到指定目录
install:
	# 将当前目录下所有的 .ko 文件复制到内核源码目录的 kmodules 子目录中
	cp *.ko $(KERNEL_DIR)/kmodules
  • 编译module,拷贝到共享目录
make modules
make install 
  • 启动QEMU

    启动命令:

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt  -m 1024 -smp 4 -kernel arch/arm64/boot/Image --append "rdinit=/linuxrc root=/dev/vda rw console=ttyAMA0 loglevel=8"  -nographic  --fsdev local,id=kmod_dev,path=$PWD/kmodules,security_model=none  -device virtio-9p-device,fsdev=kmod_dev,mount_tag=kmod_mount
回车后进入控制台,以防万一可以检查一下挂载点是否成功
ls /mnt

image-20250308163114922

​ 可以成功看到.ko文件。

  • 在QEUM执行module的插入与卸载,可以看到成功执行并打印log
[xlq@xlq-virtual-machine ]$ cd mnt
[xlq@xlq-virtual-machine mnt]$ ls /mnt
hello.ko  kmod_mount
[xlq@xlq-virtual-machine mnt]$ insmod hello.ko
[ 3679.306752] hello: loading out-of-tree module taints kerne
[ 3679.322242] hello world!
[xlq@xlq-virtual-machine mnt]$ rmmod hello.ko
3732.515746] hello exit!

image-20250308164115326

六、实验过程中遇到的一些问题及解决办法

1、本地下载压缩包如何上传Ubuntu中?

1、本地电脑上下载的busybox和Linux内核源码在移动到Ubuntu虚拟机上时拖到到主文件夹即可

在这里插入图片描述

2、make编译BusyBox出现的报错

2、出现问题:在使用命令make编译BusyBox时,出现报错信息如下

libbb/hash_md5_sha.c:1316:35: error: 'sha1_process_block64_shaNI' undeclared (first use in this function);did you mean 'sha1 process block64'?

解决办法nano libbb/hash_md5_sha.c 进入函数中,查找sha1_process_block64_shaNI,将所有的sha1_process_block64_shaNI修改成sha1_process_block64。保存后退出,再使用make编译即可通过。

3、根文件系统放到源码根目录时报错

3、出现问题:在将根文件系统放到源码根目录的时候,参考命令为:cp ~linux/rootfts rootfs_arm64 -a,但出现报错“‘无法创建特殊文件’”,原因是因为普通用户没有足够的权限来创建,设备文件和特殊文件通常需要root权限来创建或修改。

解决办法:使用sudo来提升权限

sudo cp ~/linux/rootfs rootfs_arm64 -a

4、关于内核模块在QEMU中运行出现的问题

4、当我在编译一个简单内核模块hello.c在QEMU运行测试的过程中,出现了很大问题:

出现问题:通过编译生成的内核模块hello.ko文件在成功拷贝到共享目录kmodules下时,启动QEMU,在mnt文件夹下无法看到hello.ko文件。

问题排查:合理利用大模型提出问题,主要从下面几个点来排查问题

  • 共享目录未正确挂载
//检查是否执行了挂载命令
mount -t 9p -o trans=virtio kmod_mount /mnt
//如果没有挂载,需要先挂载
mkdir -p /mnt/kmod_mount
mount -t 9p -o trans=virtio kmod_mount /mnt/kmod_mount
cd /mnt/kmod_mount
ls
//检测挂载是否成功
mount | grep kmod_mount    //如果没有输出,说明挂载失败
  • 挂载点错误(共享目录挂载到了其他目录,而不是/mnt)
//检查挂载点
mount | grep kmod_mount
//输出示例
kmod_mount on /mnt type 9p (trans=virtio)
  • 共享目录路径出错

    本人觉得这个问题可能性不大,在上面执行make install命令后,可以回到内核源码目录下的kmodules文件下查看是否存在.ko文件

    如果路径错误,修改QEMU启动命令中的path参数。
    
  • 如果上述问题都没有可以再文件系统权限问题和内核未启用9p文件系统支持问题

解决办法:本人最初遇到的问题是挂载失败,重新挂载后还是无法看到挂载点下的.ko文件,进入kmodules文件下,正确的挂载应该出现一个kmod_mount的挂载标签,和一个.ko文件,如下(不要把.ko文件放到kmod_mount文件夹中

image-20250308163512591

回到QEMU中检查目录内容:

image-20250308163749586

参考链接

用BusyBox构建根文件系统

QEMU搭建ARM64+Linux系统

qemu搭建ARM Linux环境

### 准备工作 为了能够在 x86 环境下的 Ubuntu 22.04 上成功使用 QEMU 模拟 ARM64 架构并安装和运行 Ubuntu 22.04,需先完成一系列准备工作。 #### 安装必要的软件包 确保主机已更新至最新状态,并安装所需的工具: ```bash sudo apt update && sudo apt upgrade -y sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager gcc-aarch64-linux-gnu gdb-multiarch -y ``` 上述命令会安装 KVM、libvirt 及其客户端、桥接实用程序以及用于编译 ARM64 的 GCC 工具链和多架构 GDB 调试器[^3]。 #### 配置环境变量 编辑 `/etc/profile` 文件,在文件末尾追加以下两行来设置交叉编译环境变量: ```bash export CROSS_COMPILE=/usr/bin/aarch64-linux-gnu- export ARCH=arm64 ``` 使更改生效: ```bash source /etc/profile ``` ### 创建虚拟硬盘镜像 创建一个新的 qcow2 格式的磁盘映射文件作为目标系统的根文件系统存储空间: ```bash qemu-img create -f qcow2 ubuntu-22.04-arm64.qcow2 20G ``` 此操作将生成一个大小为 20GB 的稀疏文件 `ubuntu-22.04-arm64.qcow2`,它将在后续过程中充当虚拟机的主要磁盘设备[^2]。 ### 下载 ISO 映像 获取适用于 ARM64 平台的 Ubuntu Server 版本 ISO 文件: ```bash wget https://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-arm64.iso ``` 注意这里下载的是针对较旧硬件优化过的版本;对于现代服务器级硬件,则应考虑官方发布的标准版 ISO 文件[^1]。 ### 启动虚拟机进行安装 利用之前准备好的资源启动 QEMU/KVM 来引导进入 LiveCD 或者直接开始操作系统安装过程: ```bash qemu-system-aarch64 \ -machine type=virt,accel=kvm,gic-version=max \ -cpu max \ -smp cpus=4 \ -m size=4G \ -bios ./EDKII_AARCH64_CODE.fd \ -drive file=./ubuntu-22.04-arm64.qcow2,if=none,id=disk0 \ -device virtio-blk-device,drive=disk0,bus=virtio-bus.0 \ -netdev user,id=mynet0 \ -device virtio-net-pci,netdev=mynet0 \ -cdrom ./ubuntu-20.04.1-legacy-server-arm64.iso \ -boot d ``` 以上参数指定了机器类型、CPU 类型、核心数、内存容量等选项。其中 `-bios` 参数指定 UEFI 固件路径(如果有的话),而 `-drive` 和 `-device` 组合定义了虚拟磁盘及其连接方式。最后通过 `-cdrom` 加载 ISO 映像并通过光驱启动[-^2]。 ### 网络配置 默认情况下,QEMU 使用 NAT 方式提供网络访问功能。若要实现更复杂的联网模式比如桥接网卡共享物理接口或者端口转发等功能,则需要进一步调整网络子系统的配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值