[toc]
为了便于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 源码安装
- 下载源码
下载源码可以从QEMU的官网下载:download.qemu.org。截至目前,QEMU最新的版本是4.2.0。建议选用QEMU 3.0.0
以上的版本。Ubuntu下可以使用以下命令直接下载:
wget https://download.qemu.org/qemu-3.0.0.tar.xz
- 使用源码安装
安装命令如下:
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启动之后,在终端中,我们可以使用的常见命令,如ls
、pwd
、cp
等命令都可以由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
- 选择默认config
由于编译过程会生成很多临时文件,默认会存放到对应源代码的位置,为了便于管理,这里使用O=./out
参数,其目的是将生成的临时文件和最终文件都放到out
目录下去。
mkdir out
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out defconfig
- 使用静态编译
由于Busybox提供的命令工作需要依赖一些库,如果采用默认的动态编译的话,我们就需要把这些库也添加到根文件系统中,而这样做比较繁琐,所以在编译Busybox的时候,使用静态编译。
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./out menuconfig
输入上述命令后,会出来如下界面:
默认”Setting
“已被选中,按下回车,到其子菜单内找到”--- Build Options
“,找到”Build static binary (no shared libs)
“,按下Y
键选中它(前面中括号中的*
表示已选中)。
- 指定根文件系统安装位置
Busybox编译完成后会生成一些用于制作根文件系统的文件或者文件夹,这些文件默认会生成到OUT目录
(如果未指定,OUT目录就是源代码的根目录)下的_install
文件夹下。使用下列步骤将默认的_install
目录改为install_arm64
。
在上步的”--- Build Options
“中,找到”Destination path for 'make install'
“项,按下回车,在弹出的文本框中输入”install_arm64
“。
然后保存并退出。
- 编译源码并生成根文件系统
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进程用来在系统启动后产生其他所有用户进程。它可以有很多实现方法,比较常见的是sysinit
、UpStart
和systemd
等。如果想了解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.sh
和make_linux.sh
,只需要将这两个脚本,分别放到busybox
和linux
源码目录下,分别执行即可。在公众号后台留言make
,即可获得这两个脚本。
“Linux内存管理之一 —— 环境搭建(在QEMU上运行Linux 5.4.0)”的一个回复