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」

在這裏插入圖片描述
ctrl+a + c 進入 qemu-monitor,輸入 q 退出,或者按 ctrl+a + x 退出 qemu。
在這裏插入圖片描述
出錯如下: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 等文件,所以系統的操作和易用性還有待改進。
在這裏插入圖片描述

製作成 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 讓內核繼續運行。
在這裏插入圖片描述

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 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章