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-4.x.x 安装
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
而不是 disk。initramfs
实际是一个 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 ...
。正确地启动需要一个根文件系统。
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.
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
注意:
make menuconfig
后需要修改配置,将 Build static binary (no shared libs)
选上。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
等文件,所以系统的操作和易用性还有待改进。
直接用 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指定的文件是否存在。
ramdisk_execute_command
指定的文件存在,则接下来全部由其接管。如果使用 "init=/linuxrc"
参数,此时没有 rdinit
参数,则内核中默认去找 /init
,没找到,则尝试挂载rootfs。rootfs挂载失败,则一直报错。
记录一下网上看到一个方法,可以参考,实际上是把交叉编译选项放在命令行里带进去了。
如果是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 的方法来创建 root fs,功能强大,便于定制。这里就不详述了。
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
在上述命令后加上 -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
让内核继续运行。
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这个工具的用法:
参数说明:
例如:下面命令的作用就是,将目录下的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
https://medicineyeh.wordpress.com/2016/03/29/buildup-your-arm-image-for-qemu/ ↩︎
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/ ↩︎
https://blog.csdn.net/LEON1741/article/details/54809347 ↩︎