macos qemu
brew install qemu
安装编译工具macOS Mojave(10.14.3)
$ brew install arm-linux-gnueabihf-binutils
bison on macOS is too old
$ brew install bison
$ export PATH="/usr/local/opt/bison/bin:$PATH"
安装 crosstool-ng 构建GCC编译环境
$ brew install crosstool-ng
$ export CT_NG_VER=$(brew list --versions crosstool-ng | tr ' ' '\n' | tail -1)
$ export CT_NG_VER_SHORT=${CT_NGVER%*}
安装的 crosstool-ng 的脚本文件缺少执行权限,导致无法执行,我们需要手工增加执行权限
$ chmod +x "$(brew --cellar crosstool-ng)/${CT_NG_VER}/lib/crosstool-ng-${CT_NG_VER_SHORT}/scripts/crosstool-NG.sh"
默认情况下,macOS的文件系统不区分大小写,我们需要手工创建一个区分大小写的分区
$ hdiutil create -volname "ClockworkOS" -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 30g ClockworkOS.dmg
$ hdiutil attach ClockworkOS.dmg.sparseimage -mountpoint /Volumes/ClockworkOS
$ cd /Volumes/ClockworkOS
$ mkdir arm-cortexa9_neon-linux
$ cd arm-cortexa9_neon-linux
$ ct-ng list-samples
变更x-tools存储目录
$ export HOME=/Volumes/ClockworkOS
$ ct-ng arm-cortexa9_neon-linux-gnueabihf
修复BUG Build failed in step 'Installing m4 for build'
$ brew uninstall --ignore-dependencies binutils
$ brew install binutils
安装依赖工具
$ brew install automake
$ brew uninstall --ignore-dependencies gawk
$ brew install gawk
目前编译gettext-0.19.8.1的时候写死依赖automake-1.15,但是最新的已经是automake-1.16,我们通过手工编译安装automake-1.15规避这个问题
$ wget http://ftp.gnu.org/gnu/automake/automake-1.15.tar.gz
也可从本站下载 wget https://www.mobibrw.com/wp-content/uploads/2019/03/automake-1.15.tar.gz
$ tar xvf automake-1.15.tar.gz
$ cd automake-1.15
$ bash configure
$ make && make install
$ cd ..
修改文件打开数量限制,修正错误 “extra-module.mk:11: *** Too many open files.”
$ ulimit -n 2048
'scm_new_port_table_entry' was not declared in this scope
$ sed -i "" "s/CT_GDB_CROSS_EXTRA_CONFIG_ARRAY=.*/CT_GDB_CROSS_EXTRA_CONFIG_ARRAY=\"--with-guile=no\"/g" .config
$ export PATH="/usr/local/bin:$PATH"
$ ct-ng build -j8
编译 u-boot
$ cd /Volumes/ClockworkOS
下载u-boot代码
$ git clone https://github.com/qemu/u-boot.git
$ cd u-boot
$ git checkout v2019.01 -b v2019.01
$ export PATH="/Volumes/ClockworkOS/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin:$PATH"
$ export CROSS_COMPILE=arm-cortexa9_neon-linux-gnueabihf-
$ make clean
R16又名A33 ,R16-J 代表包含Jazelle DBX
$ make vexpress_ca9x4_defconfig
fix Undefined symbols for architecture x86_64: "_PyArg_ParseTuple"
$ export HOSTLDFLAGS="-lpython -dynamclib"
$ brew install gnu-sed
fix ./tools/../lib/bch.c:66:10: fatal error: 'endian.h' file not found
$ gsed -i "s/#include $ gsed -i "s/#define cpu_to_be32 htobe32/#if defined(APPLE)\n#define cpu_to_be32 OSSwapHostToBigInt32\n#else\n#define cpu_to_be32 htobe32\n#endif/g" lib/bch.c $ gsed -i "s/#if !defined(DragonFly) && !defined(FreeBSD)/#if !defined(DragonFly) && !defined(FreeBSD) && !defined(APPLE)/g" lib/bch.c $ make ARCH=arm -j8 $ cd /Volumes/ClockworkOS $ git clone https://github.com/qemu/u-boot.git $ cd u-boot $ git checkout v2019.01 -b v2019.01 $ export PATH="/Volumes/ClockworkOS/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin:$PATH" $ export CROSS_COMPILE=arm-cortexa9_neon-linux-gnueabihf- $ make clean $ make vexpress_ca9x4_defconfig $ export HOSTLDFLAGS="-lpython -dynamclib" $ brew install gnu-sed $ gsed -i "s/#include $ gsed -i "s/#define cpu_to_be32 htobe32/#if defined(APPLE)\n#define cpu_to_be32 OSSwapHostToBigInt32\n#else\n#define cpu_to_be32 htobe32\n#endif/g" lib/bch.c $ gsed -i "s/#if !defined(DragonFly) && !defined(FreeBSD)/#if !defined(DragonFly) && !defined(FreeBSD) && !defined(APPLE)/g" lib/bch.c $ make ARCH=arm -j8 编译 Linux 内核 $ brew install aria2 $ aria2c -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.14.2.tar.xz $ tar xvf linux-4.14.2.tar.xz $ cd linux-4.14.2 $ export PATH="/Volumes/ClockworkOS/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin:$PATH" $ export PATH="/Volumes/ClockworkOS/u-boot/tools:$PATH" $ brew install libelf $ echo " $ brew install findutils $ export PATH="/usr/local/opt/findutils/libexec/gnubin:$PATH" $ ln -s /usr/local/bin/gstat /usr/local/bin/stat $ export PATH="/usr/local/bin:$PATH" $ export CROSS_COMPILE=arm-cortexa9_neon-linux-gnueabihf- $ export ARCH=arm $ make vexpress_defconfig $ make -j8 $ mkimage -A arm -O linux -T kernel -C none -a 0x40008000 -e 0x40008000 -n "Linux kernel" -d arch/arm/boot/zImage uImage $ cd /Volumes/ClockworkOS $ brew install aria2 $ aria2c -c https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.14.2.tar.xz $ tar xvf linux-4.14.2.tar.xz $ cd linux-4.14.2 $ export PATH="/Volumes/ClockworkOS/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin:$PATH" $ export PATH="/Volumes/ClockworkOS/u-boot/tools:$PATH" $ brew install libelf $ echo " $ brew install findutils $ export PATH="/usr/local/opt/findutils/libexec/gnubin:$PATH" $ ln -s /usr/local/bin/gstat /usr/local/bin/stat $ export PATH="/usr/local/bin:$PATH" $ export CROSS_COMPILE=arm-cortexa9_neon-linux-gnueabihf- $ export ARCH=arm $ make vexpress_defconfig $ make -j8 $ mkimage -A arm -O linux -T kernel -C none -a 0x40008000 -e 0x40008000 -n "Linux kernel" -d arch/arm/boot/zImage uImage $ cd /Volumes/ClockworkOS $ brew install aria2 $ aria2c -c http://clockworkpi.k15.net/clockworkos_v0.3.img.bz2 $ rm -rf clockworkos_v0.3.img $ bzip2 -d -k -vvvv clockworkos_v0.3.img.bz2 $ hdiutil attach clockworkos_v0.3.img -mountpoint /Volumes/clockworkos_v0.3 $ echo y | cp -i /Volumes/ClockworkOS/linux-4.14.2/uImage /Volumes/clockworkos_v0.3/uImage $ hdiutil detach /Volumes/clockworkos_v0.3 $ brew install qemu $ qemu-img convert -f raw -O qcow2 clockworkos_v0.3.img clockworkos_v0.3.qcow2 $ cd /Volumes/ClockworkOS $ brew install aria2 $ aria2c -c http://clockworkpi.k15.net/clockworkos_v0.3.img.bz2 $ rm -rf clockworkos_v0.3.img $ bzip2 -d -k -vvvv clockworkos_v0.3.img.bz2 $ hdiutil attach clockworkos_v0.3.img -mountpoint /Volumes/clockworkos_v0.3 $ echo y | cp -i /Volumes/ClockworkOS/linux-4.14.2/uImage /Volumes/clockworkos_v0.3/uImage $ hdiutil detach /Volumes/clockworkos_v0.3 $ brew install qemu $ qemu-img convert -f raw -O qcow2 clockworkos_v0.3.img clockworkos_v0.3.qcow2 手工编译 qemu $ git clone https://github.com/qemu/qemu.git $ cd qemu $ sed -i "" "s/[VE_NORFLASHALIAS] = 0/[VE_NORFLASHALIAS] = -1/g" hw/arm/vexpress.c $ bash configure $ make -j8 $ cd .. $ /Volumes/ClockworkOS/qemu/arm-softmmu/qemu-system-arm -M vexpress-a9 -m 1024M -kernel /Volumes/ClockworkOS/u-boot/u-boot -serial mon:stdio -nographic -sd clockworkos_v0.3.qcow2 -net nic,model=lan9118 -net user $ cd /Volumes/ClockworkOS $ git clone https://github.com/qemu/qemu.git $ cd qemu $ sed -i "" "s/[VE_NORFLASHALIAS] = 0/[VE_NORFLASHALIAS] = -1/g" hw/arm/vexpress.c $ bash configure $ make -j8 $ cd .. $ /Volumes/ClockworkOS/qemu/arm-softmmu/qemu-system-arm -M vexpress-a9 -m 1024M -kernel /Volumes/ClockworkOS/u-boot/u-boot -serial mon:stdio -nographic -sd clockworkos_v0.3.qcow2 -net nic,model=lan9118 -net user 可惜到这一步了,还是没办法成功运行系统。 本文主要介绍在 MacOS 上使用 qemu 搭建 Linux Kernel 的开发环境。(在开始之前需要注意的是,本文中的 Linux 开发环境是一个远程服务器,而 qemu 被安装在本地的 MacOS 上。通常并不需要这样折腾,直接将 qemu 安装在 Linux 中更加方便,而且 qemu 是可以 qemu 是一个硬件虚拟化程序( hypervisor that performs hardware virtualization),与传统的 VMware / VirtualBox 之类的虚拟机不同,它可以通过 binary translation 模拟各种硬件平台(比如在 x86 机器上模拟 ARM 处理器)。而 VirtualBox 等更多是通过虚拟化来进行资源隔离,以便在其上运行多个 guest os。 基于 qemu 的硬件模拟能力,我们可以轻松搭建指定硬件平台的运行实验环境。 qemu 与 VirtualBox 另一个不同点在于,在 VirtualBox 上必须安装一个完整的操作系统套件,而通过 qemu 我们可以通过参数直接启动到一个裸的 Linux Kernel,连 bootloader 都不需要关心。在此之外,按需配置相关工具套件与启动好的 Kernel 一起工作即可。 qemu 提供的这种高度可定制化的『白盒』能力,使得我们可以按需构建快速、轻量级的开发环境,提供流畅的开发体验。 首先,为了进行内核开发,需要一个现成的 Linux 操作系统环境。可以是一个通过 ssh 工作的远程 Linux Server,或者也可以在 MacOS 上通过 VirtualBox (或者使用 qemu 也可以)安装一个虚拟机用于开发。VirtualBox 的安装和 Linux Guest OS 的安装配置此处略过不提。 接下来,安装 qemu。在 MacOS 上可以使用 Homebrew 包管理工具进行安装(本文使用的 qemu 版本为 2.9.0_2): 安装完成后,可以看到系统中有很多个 上述命令会启动一个类似 VirtualBox 虚拟机启动时的窗口。当然,由于我们没有指定任何设备,最终会提示找不到可启动设备。 按需编译内核,此处只进行简单说明(基于内核 v4.13)。 可以先执行 通常先进行内核编译配置: 会启动一个基于文本的配置界面进行各种选项、模块、驱动等配置。或者也可以直接使用目标平台默认的配置,如针对 x86_64 平台(后续平台相关的地方均以 x86_64 为例进行说明)可以使用: 配置完成后相应的配置项会保存在 我们构建一个压缩过的内核镜像: 编译成功后,bzImage 文件将出现在 编译好的内核模块 编译好内核以后,我们就可以使用 qemu 启动内核了。只需要使用 上述命令假设编译好的 bzImage 内核文件就存放在当前目录下。因为之前编译好的内核文件是在 VirtualBox 的虚拟机中(或者在远程服务器上),而 qemu 在本地 MacOS 上,可以通过 VirtualBox 的 share folder 来共享目录,或者使用 NFS 共享,甚至简单使用 rsync 来在两者之间同步文件。后续关于文件同步与共享不再赘述。 不出意外的话,就可以在启动窗口中看到内核的启动日志了。在内核启动的最后,会出现一条 panic 日志: 从日志内容可以看出,内核启动到一定阶段后尝试加载根文件系统,但我们没有指定任何磁盘设备,所以无法挂载根文件系统。而且上一节中编译出来的内核模块现在也没有用上,内核模块也需要存放到文件系统中供内核需要的时候进行加载。 所以,接下来需要制作一个磁盘镜像文件供内核作为根文件系统加载。 如上一节所述,需要制作一个磁盘镜像文件作为根文件系统供内核加载,同时也用于存放编译好的内核模块,以及后续所需的各种配套工具程序。 使用 现在 disk.raw 文件就相当于一块磁盘,为了在里面存储文件,需要先进行格式化,创建文件系统。比如在 Linux 系统中使用 ext4 文件系统进行格式化: 格式化完成之后,可以在 Linux 系统中以 loop 方式将磁盘镜像文件挂载到一个目录上,这样就可以操作磁盘镜像文件中的内容了。 现在可以将之前编译好的内核模块安装到磁盘镜像中了。命令如下: 执行完成后即可在 准备好磁盘镜像文件后,使用下面的命令再次启动 qemu: 这一次,内核不再报根文件系统找不到了。但是报了另一个错误: 这说明内核启动已经接近完成了,准备启动 1 号进程,也就是 init 进程。但我们的启动参数里面没有指定 init 选项,而且磁盘镜像中也没有相应的 init 程序。因此,接下来需要准备一个 init 程序供内核启动。 常用的 init 程序有下面几种: 这里选用 busybox 作为 init 程序及其它命令工具的提供者。 下载 busybox 的源码到 Linux 系统中,准备进行编译,这里使用的 busybox 版本为 1.27.2。 busybox 的编译流程与内核很像,这里我们基于默认配置进行编译。首先,执行如下命令让默认配置生效: 接下来,在默认配置的基础上进行定制: 这里有一个重要的配置,因为 busybox 将被用作 init 程序,而且我们的磁盘镜像中没有任何其它库,所以 busybox 需要被静态编译成一个独立、无依赖的可执行文件,以免运行时发生链接错误。配置路径如下: 最后,配置完成后执行编译: 编译完成后在当前目录下可以看到 编译好 busybox 之后需要将其安装到磁盘镜像中以供使用。执行如下命令进行安装: busybox 安装完成之后,使用内核启动参数 上述命令通过 这一次内核成功找到了 init 程序并且创建出 init 进程,但是 init 执行过程中出现如下报错: 看样子,init 程序需要一些配置才能正常运行起来。 参考 busybox 代码中的 文档 可知,init 启动后会扫描 参考文档,我们提供一份 并且根据配置,我们创建可执行文件 配置完成以后再次尝试启动,这次将成功启动,并且出现如下提示: 按提示按下 Enter 键之后将会启动 shell,进行到我们熟悉的环境,可以执行各种常用命令了。 查看当前系统环境,会发现当前文件系统结构是不完整的。比如没有 /dev, /proc 以及 /sys 挂载点。这样我们无法通过 /dev 查看系统中的设备,如果执行 因此,我们需要手工创建 /dev, /proc, /sys 这三个目录。/dev 目录创建完成后重启系统即可工作,但 /proc 和 /sys 需要执行挂载才可工作,可以将 /proc 和 /sys 的挂载动作放到 重新启动系统查看,可以看到 /dev, /proc, /sys 挂载点都相应有了内容。 本文介绍了通过 qemu 作为模拟器,自己动手编译内核,并从头配置 init 进程,构建出一个最小的可运行系统,可用于验证对内核的改动。无视最后的失败提示,只要u-boot这个文件生成即可
下载u-boot代码
R16又名A33 ,R16-J 代表包含Jazelle DBX
fix Undefined symbols for architecture x86_64: "_PyArg_ParseTuple"
fix ./tools/../lib/bch.c:66:10: fatal error: 'endian.h' file not found
无视最后的失败提示,只要u-boot这个文件生成即可
$ cd /Volumes/ClockworkOS也可本站下载 wget https://www.mobibrw.com/wp-content/uploads/2019/03/linux-4.14.2.tar.xz
for mkimage
或者 brew install u-boot-tools
elf.h
#include
#define R_386_NONE 0
#define R_386_32 1
#define R_386_PC32 2
#define R_ARM_NONE 0
#define R_ARM_PC24 1
#define R_ARM_ABS32 2
#define R_MIPS_NONE 0
#define R_MIPS_16 1
#define R_MIPS_32 2
#define R_MIPS_REL32 3
#define R_MIPS_26 4
#define R_MIPS_HI16 5
#define R_MIPS_LO16 6
#define EF_ARM_EABIMASK 0xFF000000
#define EF_ARM_EABI_VERSION(flags) ((flags) & EF_ARM_EABIMASK)" > /usr/local/include/elf.hxargs: illegal option -- r
stat: illegal option -- c
也可本站下载 wget https://www.mobibrw.com/wp-content/uploads/2019/03/linux-4.14.2.tar.xz
for mkimage
或者 brew install u-boot-tools
elf.h
#include
#define R_386_NONE 0
#define R_386_32 1
#define R_386_PC32 2
#define R_ARM_NONE 0
#define R_ARM_PC24 1
#define R_ARM_ABS32 2
#define R_MIPS_NONE 0
#define R_MIPS_16 1
#define R_MIPS_32 2
#define R_MIPS_REL32 3
#define R_MIPS_26 4
#define R_MIPS_HI16 5
#define R_MIPS_LO16 6
#define EF_ARM_EABIMASK 0xFF000000
#define EF_ARM_EABI_VERSION(flags) ((flags) & EF_ARM_EABIMASK)" > /usr/local/include/elf.hxargs: illegal option -- r
stat: illegal option -- c
官方给出的这个地址下不到,只能用镜像地址 http://106.185.33.196/clockworkos_v0.3.img.bz2
替换镜像中的内核文件
官方给出的这个地址下不到,只能用镜像地址 http://106.185.33.196/clockworkos_v0.3.img.bz2
替换镜像中的内核文件
$ cd /Volumes/ClockworkOS从 qemu v2.1.0-rc1 开始,内存需要被映射到0x60000000开始的地址,更低的地址被映射为只读闪存,我们需要取消这种映射行为,否则执行的时候会报告错误
list supported machine
qemu-system-arm -machine help
从 qemu v2.1.0-rc1 开始,内存需要被映射到0x60000000开始的地址,更低的地址被映射为只读闪存,我们需要取消这种映射行为,否则执行的时候会报告错误
list supported machine
qemu-system-arm -machine help
可惜到这一步了,还是没办法成功运行系统。参考链接
-nographic
无图形界面运行的。)1. 为什么需要 qemu?
2. 环境准备
brew install qemu
qemu-system-
开头的命令,用于模拟各种硬件平台,比如 qemu-system-x86_64
。运行其中一个命令来验证安装是否成功:qemu-system-x86_64
3. 编译内核
3.1 内核编译配置
make help
可以查看 make 支持哪些 target。make menuconfig
make x86_64_defconfig
.config
文件中。下一次执行 make menuconfig
时可以 load 这份配置文件,在此基础上进行修改。3.2 编译内核和模块
make bzImage
arch/x86_64/boot/bzImage
。记住文件路径或者拷贝到一个方便的路径,便于后续启动时使用。
接下来,编译在配置阶段选择的内核模块:make modules
*.ko
文件存在于模块对应的源码目录中。4. 启动内核
-kernel
参数告诉 qemu 内核文件的位置即可:qemu-system-x86_64 \ -m 512M \ # 指定内存大小 -smp 4\ # 指定虚拟的 CPU 数量 -kernel ./bzImage # 指定内核文件路径
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0, 0)
5. 制作磁盘镜像
5.1 创建磁盘镜像文件
qemu-img
创建一个 512M 的磁盘镜像文件:qemu-img create -f raw disk.raw 512M
mkfs -t ext4 ./disk.raw
5.2 挂载磁盘镜像文件
下面的命令将磁盘镜像文件挂载到 img 目录上:sudo mount -o loop ./disk.raw ./img
5.3 安装内核模块
sudo make modules_install \ # 安装内核模块INSTALL_MOD_PATH=./img # 指定安装路径
./img/lib/modules/
下看到安装好的内核模块。5.4 使用磁盘镜像文件作为根文件系统
qemu-system-x86_64 \ -m 512M \ -smp 4\ -kernel ./bzImage \ -drive format=raw,file=./disk.raw \ # 指定文件作为磁盘 -append "root=/dev/sda" # 内核启动参数,指定根文件系统所在设备
Kernel panic - not syncing: No working init found. Try passing init= option to Kernel. See Linux Documentation/admin-guide/init.rst for guidance.
6. 准备 init 程序
ls
、cat
等。busybox 非常轻量级,可以编译出完全独立无依赖的 busybox 套件。6.1 编译 busybox
make defconfig
make menuconfig
Busybox Settings ---> --- Build Options [*] Build BusyBox as a static binary (no shared libs)
make
busybox
可执行文件,查看大小才 2.5M 左右。整个 busybox 套件只有这一个可执行文件,里面包含了若干工具。比如:./busybox ls -l./busybox ps
6.2 安装 busybox 到磁盘镜像
make CONFIG_PREFIX=<path_to_disk_img_mount_point> install
CONFIG_PREFIX
用于指定安装路径,需要指定到之前磁盘镜像文件的挂载目录,比如 ./img
。进入磁盘镜像挂载目录查看,常见的文件系统结构已经建立起来了。查看 bin 和 sbin 目录下的命令,可以看到都是链接到bin/busybox
的,busybox 会根据执行时的文件名来执行不同的功能。6.3 使用 busybox 作为 init 程序
init=
来指定 busybox 作为 init 程序,再次尝试启动。qemu-system-x86_64 \ -m 512M \ -smp 4\ -kernel ./bzImage \ -drive format=raw,file=./disk.raw \ -append "init=/linuxrc root=/dev/sda"
init=/linuxrc
指定了 init 程序为根目录下的 linuxrc,实际上是一个指向 busybox 的软链接。can't run '/etc/init.d/rcS': No such file or directory can't open /dev/tty3: No such file or directorycan't open /dev/tty4: No such file or directory
6.4 配置 busybox init
/etc/inittab
配置文件,这个配置文件决定了 init 程序的行为。而 busybox init 在没有/etc/inittab
文件的情况下也能工作,因为它有默认行为。它的默认行为相当于如下配置:::sysinit:/etc/init.d/rcS::askfirst:/bin/sh::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r::restart:/sbin/inittty2::askfirst:/bin/shtty3::askfirst:/bin/shtty4::askfirst:/bin/sh
/etc/inittab
配置文件如下:::sysinit:/etc/init.d/rcS::askfirst:/bin/ash::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r::restart:/sbin/init
/etc/init.d/rcS
,内容如下(暂时什么事都不做):#!/bin/sh
Please press Enter to activate this console.
6.5 挂载 /dev, /proc, /sys 文件系统
df
命令也会因为没有 /proc 挂载点而报错:df: /proc/mounts: No such file or directory
/etc/init.d/rcS
中,每次系统启动时自动挂载。修改 /etc/init.d/rcS
内容如下:#!/bin/shmount -t proc proc /procmount -t sysfs sysfs /sys
7. 小结
通过这次开发环境搭建,对系统的启动过程有了一个粗略的了解。但这只是迈出了第一步,后续还有长路漫漫。