Linux aarch64 编译 & qemu 搭建实验平台

交叉编译工具链

linaro 官网下载即可。https://www.linaro.org/downloads/
https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/
解压以后在环境变量 PATH 里加上工具链的路径。
export PATH=/path/to/bin/linaro-xxx/bin:$PATH
验证一下:aarch64-linux-gnu-gcc -v

内核编译

下载:

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.10.tar.xz

在根目录下会产生 vmlinux*,arch/arm64/boot/下会产生 Image
vmlinux 编出来大概有190+M,Image 去除了很多调试信息,大概是15M。

cd ~/linux-4.10/

# cp ./arch/arm64/configs/defconfig  .config

mkdir build

# stay in the current dir, do not cd to ./build
# 如果需要调整配置选项,则使用 menuconfig 进行配置
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./build defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./build menuconfig
# 注意这里 menuconfig 需要选中以下两个选项,这里给 ramdisk 64M的空间,应当比后面生成的 rootfs 大。
	General setup  --->
	    [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
	 
	Device Drivers  --->
	    [*] Block devices  --->
	        <*>   RAM block device support
	        (65536) Default RAM disk size (kbytes)
        
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- O=./build Image -j16

qemu 安装

请参考 qemu-4.x.x 安装

qemu-system-aarch64 运行

Linux 要如何启动?1

Linux kernel在自身初始化完成之后,需要能够找到并运行第一个用户程序(这个程序通常叫做“init”程序)。用户程序存在于文件系统之中,因此,内核必须找到并挂载一个文件系统才可以成功完成系统的引导过程。在grub中提供了一个选项“root=”用来指定第一个文件系统,但随着硬件的发展,很多情况下这个文件系统也许是存放在USB设备,SCSI设备等等多种多样的设备之上,如果需要正确引导,USB或者SCSI驱动模块首先需要运行起来,可是不巧的是,这些驱动程序也是存放在文件系统里,因此会形成一个悖论。

为解决此问题,Linux kernel 提出了一个RAM disk的解决方案,把一些启动所必须的用户程序和驱动模块放在RAM disk中,这个RAM disk看上去和普通的disk一样,有文件系统,有cache,内核启动时,首先把RAM disk挂载起来,等到init程序和一些必要模块运行起来之后,再切到真正的文件系统之中。

上面提到的RAM disk的方案实际上就是 initrd。 如果仔细考虑一下,initrd 虽然解决了问题但并不完美。 比如,disk 有cache 机制,对于 RAM disk 来说,这个cache机制就显得很多余且浪费空间;disk 需要文件系统,那文件系统(如ext2等)必须被编译进kernel而不能作为模块来使用。

Linux 2.6 kernel 提出了一种新的实现机制,即 initramfs。顾名思义,initramfs 只是一种 RAM filesystem 而不是 diskinitramfs 实际是一个 cpio 归档,启动所需的用户程序和驱动模块被归档成一个文件。因此,不需要 cache,也不需要文件系统。

QEMU has a command argument called “-kernel”. It is a very handy function!! Because of this feature, we don’t need to bother the complicated boot sequence and problems on locating Kernel Image. QEMU will uncompress the kernel image to a proper memory location and start to run the kernel code.

很显然指定 -kernel /path/to/kernel_image 即可。但是这样是无法正常启动 Linux 的。
qemu-system-aarch64 -kernel build/arch/arm64/boot/Image -append "console=ttyAMA0" -m 2048M -smp 4 -M virt -cpu cortex-a57 -nographic

qemu-system-aarch64 \
    -kernel build/arch/arm64/boot/Image \
    -append "console=ttyAMA0" \
    -m 2048M -smp 4  \
    -M virt -cpu cortex-a57 \
    -nographic

-m 指定内存大小
-M 指定虚拟机器「machine」的类型
-cpu 指定虚拟CPU的型号
-smp 指定对称多处理的核心数
-append 指定内核启动时使用的命令行参数「cmdline」

Linux aarch64 编译 & qemu 搭建实验平台_第1张图片
ctrl+a + c 进入 qemu-monitor,输入 q 退出,或者按 ctrl+a + x 退出 qemu。
Linux aarch64 编译 & qemu 搭建实验平台_第2张图片
出错如下:kernel panic ... unable to mount root fs ...。正确地启动需要一个根文件系统。

创建 root fs

Kernel modules are mostly drivers, both hardware drivers and software drivers. For example, the Ethernet! If the driver is a kernel module stored in root file system, Linux kernel will not be able to access the Internet before mounting the root file system. Another example is ext3, ext4 driver, Linux Kernel must contain these basic file system driver in order to execute init procedure because the init files are located in root file system. It’s somehow a very common problem which was very popular in early years. That’s why we have so-called initramfs or rootfs. They are minimal file system images containing all kernel modules(.ko files), init procedure scripts, and necessary binaries to boot a full system.

busybox2

wget https://busybox.net/downloads/busybox-1.30.1.tar.bz2
tar -xjf busybox-1.30.1.tar.bz2
cd busybox-1.30.1
make defconfig
make menuconfig
make -j16
make install

注意:

  1. 执行 make menuconfig 后需要修改配置,将 Build static binary (no shared libs) 选上。
  2. 对于要在非host平台运行的情况,其交叉编译工具链也要配好!
Build Options  --->
    [*] Build BusyBox as a static binary (no shared libs)

交叉编译选项别忘了
(/path/to/aarch64-linux-gnu-) Cross Compiler prefix

此法亲测可行。

BUSYBOX_VERSION=1.30.1

dd if=/dev/zero of=busybox-${BUSYBOX_VERSION}-rootfs_ext4.img bs=1M count=64 oflag=direct
mkfs.ext4 busybox-${BUSYBOX_VERSION}-rootfs_ext4.img
mkdir rootfs
sudo mount busybox-${BUSYBOX_VERSION}-rootfs_ext4.img rootfs/
sudo cp -raf busybox-${BUSYBOX_VERSION}/_install/* rootfs/

cd rootfs
sudo mkdir -p proc sys tmp root var mnt dev
sudo mknod dev/tty1 c 4 1
sudo mknod dev/tty2 c 4 2
sudo mknod dev/tty3 c 4 3
sudo mknod dev/tty4 c 4 4
sudo mknod dev/console c 5 1
sudo mknod dev/null c 1 3
sudo cp -r ../busybox-${BUSYBOX_VERSION}/examples/bootfloppy/etc/ .

cd ..
sudo umount rootfs

运行,起飞!

这里Image的路径被我移动过了,大家不要误会 =.=
注意这里启动设备是 -hda ,use ‘file’ as IDE hard disk 0/1 image。

qemu-system-aarch64 \
        -kernel ../linux-4.10/build/Image \
        -nographic \
        -append "root=/dev/vda console=ttyAMA0 rootfstype=ext4 init=/linuxrc rw" \
        -m 2048M \
        -smp 4  \
        -M virt \
        -cpu cortex-a57 \
        -hda busybox-1.30.1-rootfs_ext4.img

-M: Specify the machine type. Use “-M help” to list all the supported boards
-kernel: Specify the kernel image (bzimage)
-dtb: Specify the hardware description file (Device Tree Blob)
-nographic: Run QEMU without GUI. It’s much more convenient.
-append: Specify Linux kernel arguments. Here we set default console to ttyAMA0 which is one of QEMU’s console when Guest OS/Applications wants to print something on host’s terminal.
-drive: Specify a drive for the image. It can be SD card, flash, etc. It’s the lowest level of drive API. We use if(interface) SD card with write back cache policy to save image access time.
-sd: It is a higher level API to specify a drive. It’s equivalent to “-drive if=sd,file=”
-net nic,macaddr=$macaddr: Specify the mac address
-net tap,vlan=0,ifname=tap0: Use tap device for internet access
-snapshot: Don’t write back to the original disk image.

如下图所示,运行成功!可以用 ls 命令看下目录情况,由于未作任何系统配置,比如 /etc/passwd/etc/group/etc/shadow/etc/hostname 等文件,所以系统的操作和易用性还有待改进。
Linux aarch64 编译 & qemu 搭建实验平台_第3张图片

制作成 initramfs

直接用 cpio 打包压缩即可。

#cd rootfs
#find . -print0 | cpio --null -ov --format=newc  | gzip -9  > ../initramfs.cpio.gz


# or use this way

cd ../linux-4.10/build
# should cd to build, because the script use the related path,
# and gen_init_cpio is under build/usr/

sh ../scripts/gen_initramfs_list.sh \
        -o ../../run_linux/initramfs.cpio.gz ../../run_linux/rootfs2/

注意这里启动设备换成了 -initrd,use ‘file’ as initial ram disk。

qemu-system-aarch64 -kernel build/arch/arm64/boot/Image -initrd initramfs.cpio.gz -append "console=ttyAMA0 rdinit=/linuxrc" -M virt -cpu cortex-a57 -nographic -smp 4 -m 2048M


qemu-system-aarch64 \
	-kernel build/arch/arm64/boot/Image \
	-initrd initramfs.cpio.gz \
	-append "console=ttyAMA0 rdinit=/linuxrc" \
	-M virt \
	-cpu cortex-a57 \
	-nographic \
	-smp 4 \
	-m 2048M

注意:通常引导内核时向command line传递的参数都是 init=xxx ,而对于 initrd 则是传递 rdinit=xxx 。处理代码位于 init/main.c。rdinit=xxx 在内核中被 ramdisk_execute_command 变量接收,如果没有 rdinit 参数,ramdisk_execute_command默认为"/init"。sys_access() 检查ramdisk_execute_command指定的文件是否存在。

  1. 如果不存在的话,说明 ramdisk 中没有什么好执行的,使用 prepare_namespace() 准备根文件系统,因为要执行"init=xxx"指定的程序了。
  2. 如果 ramdisk_execute_command 指定的文件存在,则接下来全部由其接管。

如果使用 "init=/linuxrc" 参数,此时没有 rdinit 参数,则内核中默认去找 /init,没找到,则尝试挂载rootfs。rootfs挂载失败,则一直报错。

  • 解压和制作ramdisk:
    解压 gunzip -c …/initrd-cpio.gz | cpio -i
    制作 find . | cpio -o -H newc | gzip > …/ramdisk.cpio.gz

其他方法

记录一下网上看到一个方法,可以参考,实际上是把交叉编译选项放在命令行里带进去了。
如果是x86_64 的话,不需要 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- install

制作根文件系统。

sudo mkdir rootfs
sudo cp _install/* -r rootfs/
sudo mkdir rootfs/lib
sudo cp -P /usr/arm-linux-gnueabi/lib/* rootfs/lib/

sudo mknod rootfs/dev/tty1 c 4 1
sudo mknod rootfs/dev/tty2 c 4 2
sudo mknod rootfs/dev/tty3 c 4 3
sudo mknod rootfs/dev/tty4 c 4 4

dd if=/dev/zero of=a9rootfs.ext3 bs=1M count=32
mkfs.ext3 a9rootfs.ext3

sudo mkdir tmpfs
sudo mount -t ext3 a9rootfs.ext3 tmpfs/ -o loop
sudo cp -r rootfs/*  tmpfs/
sudo umount tmpfs

qemu-system-arm -M vexpress-a9 -m 512M -kernel /home/peter/work/src/linux/linux/arch/arm/boot/zImage -nographic -append "root=/dev/mmcblk0 console=ttyAMA0" -sd a9rootfs.ext3

Buildroot

推荐使用 Buildroot 的方法来创建 root fs,功能强大,便于定制。这里就不详述了。

x86_64

x86_64的同理,不过命令有些差别,这里也记录一下。根文件系统可以使用系统带的命令进行生成。

qemu-system-x86_64 -kernel ./bzImage -nographic -append "console=tty0" -initrd ramdisk.img -m 512

mkinitramfs -o ramdisk.img

qemu-system-x86_64 \
    -kernel ./bzImage \
    -nographic \
    -append "console=tty0" \
    -initrd ramdisk.img \
    -m 512

gdb 调试

在上述命令后加上 -s-S。前者是 -gdb tcp::1234 的缩写,后者表示 freeze CPU at startup (use 'c' to start execution)。运行后打开 gdb,加载内核调试文件,运行target remote :1234 attach 到 qemu 里的调试端口,使用 hbreak start_kernel 在 start_kernel 打个断点。然后 c 让内核继续运行。
Linux aarch64 编译 & qemu 搭建实验平台_第4张图片

Image zImage uImage3

1、首先来解释一下前面2个命令的区别。Image为普通的内核映像文件,而zImage为压缩过的内核映像文件(其中的z字母就是压缩的意思)。一般情况下,编译出来的Image大约为4M,而zImage不到2M。

2、然后来解释一下第3个命令uImage。它是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。换句话说,如果直接从uImage的0x40位置开始执行,那么zImage和uImage没有任何区别。

为什么要用uboot 的mkimage工具处理内核映像zImage呢?

因为uboot在用bootm命令引导内核的时候,bootm需要读取一个64字节的文件头,来获取这个内核映象所针对的CPU体系结构、OS、加载到内存中的位置、在内存中入口点的位置以及映象名等等信息。这样bootm才能为OS设置好启动环境,并跳入内核映象的入口点。而mkimage就是添加这个文件头的专用工具。具体的实现请看uboot中bootm的源码和mkimage的源码。

下面介绍下mkimage这个工具的用法:

参数说明:

  • A:指定 CPU 的体系结构,有:alpha、arm 、x86、ia64、mips、mips64、 ppc 、s390、sh、sparc 、sparc64、m68k 等;
  • O:指定操作系统类型,有:openbsd、netbsd、freebsd、4_4bsd、linux、 svr4、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos;
  • T:指定映象类型,有:standalone、kernel、ramdisk、multi、firmware、script、filesystem;
  • C:指定映象压缩方式,有:
    :none 不压缩(一般使用这个,因为 zImage 是已经被 bzip2 压缩过的自解压内核);
    :zip 用 gzip 的压缩方式;
    :bzip2 用 bzip2 的压缩方式;
  • a:指定映象在内存中的加载地址,映象下载到内存中时,要按照用 mkimage 制作映象时该参数所指定的地址值来下载;
  • e:指定映象运行入口点地址,这个地址就是-a 参数指定值加上 0x40(因为前面有个 mkimage 添加的 0x40 个字节的头);
  • n:指定映象名;
  • d:指定制作映象的源文件;

例如:下面命令的作用就是,将目录下的zImage文件制作成符合uboot引导要求的uImage.img文件,使得uboot能够正确的引导和启动linux内核。-e 的地址特别要小心,在 -a 指定的地址基础上加 0x40

mkimage -n ‘mykernel’ -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage uImage.img


  1. https://medicineyeh.wordpress.com/2016/03/29/buildup-your-arm-image-for-qemu/ ↩︎

  2. https://chasinglulu.github.io/2019/07/27/%E5%88%A9%E7%94%A8Qemu-4-0%E8%99%9A%E6%8B%9FARM64%E5%AE%9E%E9%AA%8C%E5%B9%B3%E5%8F%B0/ ↩︎

  3. https://blog.csdn.net/LEON1741/article/details/54809347 ↩︎

你可能感兴趣的:(ARM)