Linux内存管理之一 —— 环境搭建(在QEMU上运行Linux 5.4.0)

为了便于Linux内存管理的学习,我们可以使用QEMU来运行Linux,这样做的好处有两点。其一,避免在物理开发板上修改代码不当,导致机器无法正常启动,需要重烧,或者重装系统的繁琐。其二,可以方便的使用GDB在运行中调试。QEMU是一款“免费的可执行硬件虚拟化的(hardware virtualization)开源托管虚拟机(VMM)”。它可以仿真多种硬件平台,通过与KVM(kernel-based virtual machine开源加速器)一起使用进而接近本地速度运行虚拟机。由于它支持多种硬件平台,我们这里以在QEMU上模拟ARM64平台,并运行Linux 5.4.0为例来介绍。

一 安装QEMU

和Ubuntu上传统安装软件的方法一样,QEMU的安装也可以使用两种方法安装:使用apt-get安装使用源码安装。下面分别介绍这两种安装方法。

1.1 apt-get安装

只需要在终端下输入以下命令即可完成安装:

sudo apt-get update
sudo apt-get install qemu

1.2 源码安装

  1. 下载源码
    下载源码可以从QEMU的官网下载:download.qemu.org。截至目前,QEMU最新的版本是4.2.0。建议选用QEMU 3.0.0以上的版本。Ubuntu下可以使用以下命令直接下载:
wget https://download.qemu.org/qemu-3.0.0.tar.xz
  1. 使用源码安装
    安装命令如下:
tar xvJf qemu-3.0.0.tar.xz
cd qemu-3.0.0
mkdir -p build
cd build
../configure --target-list=arm-softmmu,i386-softmmu,x86_64-softmmu,aarch64-linux-user,arm-linux-user,i386-linux-user,x86_64-linux-user,aarch64-softmmu --audio-drv-list=alsa --enable-virtfs
make && sudo make install

注意:QEMU的版本和Ubuntu的版本需要做适当的对应,否则,QEMU可能无法正常启动。比如我的Ubuntu版本是Ubuntu 16.04.3 LTS,使用1.1 apt-get安装安装的QEMU版本是2.xx,在完成安装,最终启动Linux时,不显示任何log,也不报错,就是无法启动。后来升级到QEMU 3.0.0之后,就可以正常启动。

二 编译Busybox

Busybox是一个可运行于多款POSIX环境的应用程序,它在单一的可执行文件中提供了精简的Unix工具集。简单来讲,Linux启动之后,在终端中,我们可以使用的常见命令,如lspwdcp等命令都可以由Busybox提供的,甚至于,终端对于Linux也是一个应用程序,它也可以由Busybox提供。

2.1 源码下载

下载Busybox的源码,我们同样也可以到其官网下载:https://busybox.net/downloads/。这里我们使用最新的Busybox版本1.31.1。下载命令如下:

wget https://busybox.net/downloads/busybox-1.31.1.tar.bz2

2.2 编译源码

由于最终我们要实现用QEMU模拟Arm64平台,来运行Linux,因此在编译时,应该指定目标平台的架构为arm64,同时使用对应的交叉编译工具前缀:aarch64-linux-gnu-。具体过程如下:
1. 解压源码

tar xvjf busybox-1.31.1.tar.bz2
cd busybox-1.31.1
  1. 选择默认config
    由于编译过程会生成很多临时文件,默认会存放到对应源代码的位置,为了便于管理,这里使用O=./out参数,其目的是将生成的临时文件和最终文件都放到out目录下去。
mkdir out
make  ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out defconfig
  1. 使用静态编译
    由于Busybox提供的命令工作需要依赖一些库,如果采用默认的动态编译的话,我们就需要把这些库也添加到根文件系统中,而这样做比较繁琐,所以在编译Busybox的时候,使用静态编译。
make  ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out menuconfig

输入上述命令后,会出来如下界面:

menuconfig

默认”Setting“已被选中,按下回车,到其子菜单内找到”--- Build Options“,找到”Build static binary (no shared libs)“,按下Y键选中它(前面中括号中的*表示已选中)。

menuconfig-static-binary

  1. 指定根文件系统安装位置
    Busybox编译完成后会生成一些用于制作根文件系统的文件或者文件夹,这些文件默认会生成到OUT目录(如果未指定,OUT目录就是源代码的根目录)下的_install文件夹下。使用下列步骤将默认的_install目录改为install_arm64
    在上步的”--- Build Options“中,找到”Destination path for 'make install'“项,按下回车,在弹出的文本框中输入”install_arm64“。

menuconfig-install-dir

然后保存并退出。

  1. 编译源码并生成根文件系统
make  ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out install

三 制作根文件系统

根文件系统是Linux系统启动时挂载的第一个文件系统,它里面保存着系统启动过程的需要的初始化脚本和服务等文件。详细的理解请参考参考阅读1参考阅读2。上述编译Busybox生成了根文件系统的部分文件,根据下面的步骤,我们将基于Busybox的结果建立一个相对完整的根文件系统。

3.1 创建必要的文件夹及设备节点

进入Busybox的安装目录下,执行以下命令,创建根文件系统所需的文件及设备节点:

cd busybox-1.31.1/out/install_arm64
mkdir -pv {bin,dev,d,sbin,etc/init.d,mnt,proc,sys,usr/{bin,sbin},tmp}
sudo mknod dev/null c 1 3
sudo mknod dev/console c 5 1

3.2 创建init进程

Linux系统的第一个用户进程是init的进程,kernel启动后,会去根文件系统的几个位置查找名为init的可执行程序,来作为其init进程执行。init进程用来在系统启动后产生其他所有用户进程。它可以有很多实现方法,比较常见的是sysinitUpStartsystemd等。如果想了解init进程的更多信息,请参考参考阅读3参考阅读4。这里我们主要研究内存管理,所以只要确保系统启动后能够进入shell就行了。因此我们可以自定义init程序的实现,但是Busybox其实已经创建了一个精简版的init程序—— linuxrc,它位于上述我们指定的out/install_arm64中。因此,我们只要将其重命名为init即可。

mv linuxrc init

3.2 创建fstab

etc/下创建fstab文件,写入以下内容:

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 /d debugfs defaults 0 0

3.3 创建rcS脚本

etc/init.d/下创建rcS文件,写入以下内容:

/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

# mount net9p fs
mkdir -pv /mnt/net9p
mount -t 9p -o trans=virtio net9p /mnt/net9p

至此,一个基本完整的根文件系统就创建完成了。

四 编译kernel

如同编译Busybox一样:由于最终我们要实现用QEMU模Arm64平台,来运行Linux,因此,在编译时,应该指定目标平台的架构为arm64,同时使用对应的交叉编译工具前缀:aarch64-linux-gnu-

4.1 生成kernel配置文件

Kernel中很多功能其实是通过配置文件来控制的,对于不同平台的kernel当然也有不同的配置,具体的配置方法可以参考参考阅读5参考阅读6。这里我们使用arm64架构对应的默认配置(arch/arm64/configs/defconfig),命令如下:

mkdir out
make  ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out defconfig

同样,为了便于管理,我们为生成的中间文件和最终文件创建了一个out目录。执行完上述命令后,将会根据arch/arm64/configs/defconfig的配置在out/目录下生成一个.config的文件。

4.2 指定根文件系统的位置

kernel在编译之前唯一需要设置的是给kernel指定根文件系统存放位置。在第三节中,我们已经创建了一个基本的根文件系统,并存放于:busybox-1.31.1/out/install_arm64下,因此,我们只需要把其完整路径传递给kernel即可,传递方法就是修改kernel的配置文件。

根据参考阅读6,修改kernel配置的方法有多种,我们可以采用常用的类似于Busybox那样使用make menuconfig来修改kernel的配置,这里为了方便起见,我们采用另外一种修改配置的方法merge_config,它依靠kernel提供的脚本scripts/kconfig/merge_config.sh来实现,简单来说,就是在使用make defconfig生成了.config之后,我们再创建另外一个config文件,比如my.config,在其中写入我们要修改的config的值,然后执行merge_config,完成merge。具体如下:

touch out/my.config

将下列内容写入my.config:

CONFIG_INITRAMFS_SOURCE="busybox-1.31.1/out/install_arm64"
CONFIG_INITRAMFS_ROOT_UID=0
CONFIG_INITRAMFS_ROOT_GID=0
CONFIG_INITRAMFS_COMPRESSION=".gz"

注意,上述busybox-1.31.1/out/install_arm64要替换成真实的install_arm64所在的完整路径。

执行merge操作:

scripts/kconfig/merge_config.sh -m -O out out/.config out/my.config

上述config将被写入到out/.config中,从而完成配置。

4.3 编译kernel

使用下面的命令编译kernel:

make  ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out

编译完成之后,就得到了一个已经打包了我们制作的根文件系统的kernel的image,位于 out/arch/arm64/boot/Image.gz

五 QEMU上运行kernel

在QEMU上运行kernel的只需要在kernel代码的根目录下执行下列命令:

qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic -smp 1 -m 2048 -kernel out/arch/arm64/boot/Image

为了方便的使用脚本去制作根文件系统和编译kernel,我们制作了两个脚本make_busybox.shmake_linux.sh,只需要将这两个脚本,分别放到busyboxlinux源码目录下,分别执行即可。在公众号后台留言make,即可获得这两个脚本。

六 参考阅读

  1. 根文件系统理解
  2. Root Filesystem Definition
  3. 什么是 Init 系统,init 系统的历史和现状
  4. 维基百科 init
  5. Kernel Documetation kbuild
  6. Linux内核配置系统浅析

发表评论

电子邮件地址不会被公开。 必填项已用*标注